diff --git a/.gitignore b/.gitignore index 1e2af8e..4fb69fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,51 @@ -*/target +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +# IAT Items .DS_Store -.env +docker/.env +*/target/* +jet/ + +# Web Directories +web/client/.angular/ +web/client/dist/ +web/client/node_modules/ + +# Acii Directories +acii/acii-client/logs/ +acii/acii-client/build/ + +# Inference Directories +inference/logs/ +inference/__pycache__/ +inference/*.out +inference/*.png +inference/*.sh + +# BIQT Directories +biqt/build/ +biqt/.idea/ +biqt/*.iml +biqt/target/ + +# Tshepii Directories +tshepii/build/ +tshepii/.idea + +# PDM Directories +pdm/__pycache__/ +pdm/models/ +pdm/modules/__pycache__/ +pdm/data/ +pdm/.DS_Store +pdm/.env +pdm/*venv* +pdm/pdm-models* \ No newline at end of file diff --git a/README.md b/README.md index 1415f58..4f9f5a0 100644 --- a/README.md +++ b/README.md @@ -1,150 +1,127 @@ -# NOTICE # +> +> NOTICE +> +> This software (or technical data) was produced for the U. S. Government and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV (May 2014) - Alternative IV (Dec 2007). +> +> (c) 2024 The MITRE Corporation. All Rights Reserved. +> -Only artifacts necessary for running the containerized version of the Iris Analysis Tookit stack are currently available. Code will be published soon. Please see [Quickstart for Users](#quickstart-for-users) below for running the software with `Docker`. -# Iris Analysis Toolkit # +# Iris Analysis Toolkit The Iris Analysis Toolkit (IAT), formerly known as the Iris Workstation and Iris Workstation Prototype, is a system that demonstrates the capabilities of an iris examination workstation. It was made to allow users to easily create generic Electronic Biometric Transmission Specification files as well as review and annotate iris images. -## Table of Contents ## +## Table of Contents - [Iris Analysis Toolkit](#iris-analysis-toolkit) - [Table of Contents](#table-of-contents) - [Software Versions](#software-versions) + - [Build Everything from Source](#build-everything-from-source) - [Quickstart for Users](#quickstart-for-users) - - [QuickStart for Developers](#quickstart-for-developers) - - [MacOS Installation](#macos-installation) - - [Start Up](#start-up) -## Software Versions ## +## Software Versions -This program was built using these package managers, but may work with newer versions: +This project was built with the following tools and may work with newer versions: -- Development Platform: Angular Cli -v 17.3.1 -- Runtime Environment: node -v >= 20.15.0 -- Angular Package Manager: npm -v 10.8.1 -- Java SE Environment: java -v 17 -- Build Package: maven -v 3.9.6 +- Development platform: Angular CLI 17.3.1 +- Runtime environment: Node.js 20.15.0 or later +- Angular package manager: npm 10.8.1 +- Java SE environment: Java 17 +- Build tool: Maven 3.9.6 +- JavaScript package manager: Yarn 1.22.22 -## Quickstart for Users ## +## Build Everything from Source -This section gives steps to start the program for users who are simply looking to run the application (i.e., are not troubleshooting or developing). Since this section only runs docker containers, it doesn't run any changes made on the local environment. +Use the provided build script to build every IAT component from a clean source checkout. The script builds the public JET and BIQT dependencies, packages the Maven modules, downloads the PDM models, and builds local CPU Docker images for ACII, BIQT, Iris Annotation, TSHEPII, PDM, and the Web component. -1. Start Up Project - 1. **IMPORTANT!** The `docker compose` files in the `iwp-simple` directory will use credentials from a `.env` file in that directory. For security reasons, a `.env` file is not included in this repo. To use the `docker compose` files you will need to create a `.env` file in the `iwp-simple` directory with the following contents: - - DATABASE_PASSWORD=A_STRONG_PASSWORD_OF_YOUR_CHOOSING - DATABASE_USERNAME=A_USERNAME_OF_YOUR_CHOOSING - DATABASE_ROOT_PASSWORD=ANOTHER_STRONG_PASSWORD_OF_YOUR_CHOOSING +### Prerequisites - See [this link](https://docs.docker.com/compose/environment-variables/set-environment-variables/#compose-file) for details about .env files - 2. In the terminal, type the following commands to start up docker containers: +- Docker with BuildKit enabled. Docker Desktop users should enable Linux/AMD64 emulation when building on Apple Silicon. +- Git, Java 17, Maven, and Bash. +- Internet access to clone public dependencies and download Maven, Python, PDM-model, and container-image dependencies. +- Enough disk space for Maven caches, model files, and Docker build layers. - ```bash - cd iris-workstation/iwp-simple/ #Navigates into to the subdirectory that contains the docker scripts for easy running - ./composeScript.sh prune #Clear out old/unwanted data and unused containers, images, etc: - docker compose up #Runs the docker script for easy start up - ``` - -2. Use Project - 1. Open choice of web browser and navigate to `localhost:8080` - 1. Log in with username and password - 2. Start generating and annotating files - -## QuickStart for Developers ## - -This section gives steps to start the program in the "editing mode". The following steps are for developers who are troubleshooting or contributing to the code. This section starts up the program to run only the services in docker containers. The backend and frontend parts are built and ran using the code from the local environment. - -### MacOS Installation ### - -Follow the steps below to get started with this project's development environment on a Mac. - -1. Install Xcode Command Line Tools - 1. Open Terminal and type the following command: +If your network uses a TLS-inspecting proxy, provide its PEM root certificate with `IAT_CA_FILE`. The script passes the certificate to image builds as a BuildKit secret so the affected build stages can add it to their trust stores. - ```bash - xcode-select --install - ``` +### Build - 2. In the new dialog windows, confirm and agree to the installation and license agreement -2. Install Homebrew - 1. Open Terminal and type the following command: +From the repository root, run: - ```bash - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - ``` +```sh +./scripts/build-all.sh +``` - 2. Type your admin password and hit Enter[^1] - 3. Wait a few minutes until you see a "Installation successcul" message - [^1]: You won't see your keystrokes in the terminal -3. Install Packages - - Open Terminal and type the following commands to install the packages: +For a TLS-inspecting network, run: - ```bash - brew install angular-cli #Angular Cli - brew install node@20.15.0 #Node - brew install maven #Maven - ``` - -### Start Up ### - -1. Clone Project -2. Start Up Project: - 1. In the terminal, type the following commands to start up the docker containers: - - ```bash - cd iris-workstation/iwp-simple/ #Navigates into to the subdirectory that contains the docker scripts for easy running - ./composeScript.sh prune #Clear out old/unwanted data and unused containers, images, etc. - docker compose -f docker-compose.local.yml up #Runs a docker script that only starts up the services from docker containers - ``` +```sh +IAT_CA_FILE=/path/to/organization-root-ca.pem ./scripts/build-all.sh +``` - 2. Open a second terminal window and type the following commands to start up the backend: +The script clones the public dependencies into a sibling `iat-dependencies/` directory by default. Set `IAT_DEPENDENCY_ROOT` to use another location. It tags the local images with the same names and versions used by `docker/docker-compose.yml`, so Docker Compose uses the images you just built. The default target platform is `linux/amd64`, matching the Compose configuration. - **After this step you can access the application at [http://localhost:8080](http://localhost:8080).** +The script builds the CPU Iris Annotation image. To build the GPU variant instead, run the equivalent command after the script completes: - ```bash - cd iris-workstation/ #Navigates to the project's home directory - UNIX: export JASYPT_ENCRYPTOR_PASSWORD="examplepass" - Windows: $Env:JASYPT_ENCRYPTOR_PASSWORD ="examplepass" - ./iwp-script.sh full #Runs the local backend services, including any changes just made - ``` +```sh +docker build -t ghcr.io/mitre/iat/iwp-annotation:26.08 \ + -f inference/core/Dockerfile-GPU inference/core +``` - 3. **(Optional)** Open a third terminal window and type the following commands to start up the frontend: +After the build completes, create `docker/.env` from `docker/.env.template`, provide strong non-empty credentials and a database name, and start the local images with `docker compose up` from `docker/`. - **You only need this step if you want access to a front end that automatically refreshes when changes are made.** - - After this step you can access the front end development server at [http://localhost:4200](http://localhost:4200). +## Quickstart for Users - **Special note about the front end development server:** Due to some Spring Security and webpack-dev-server proxy complexities, the login page does not work through the development server. You likely will see strange and broken behavior on the front end development server. There are two workarounds for this: +This section explains how to start the application for users who only need to run it and are not troubleshooting or developing. Because it runs only Docker containers, local source-code changes are not included. - - Disable the login page while doing front end development by setting `iwp.security.secured=false` in the `application.properties` file. +### Set up Environment Variables +1. Start the project. + **Important:** The Docker Compose files in the `docker` directory and the `application.properties` files in the `web` directory use credentials from a `.env` file in the `docker` directory. For security reasons, the repository includes only a `.env.template` file. To run the application with Docker or locally, follow these steps: + 1. Make a new file in the `docker` directory called `.env` from the `.env.template` file by running: + ```sh + cp .env.template .env + ``` + 2. Set the variables in the new `.env` file to usernames and passwords of your choosing. The database and Artemis passwords do not need to be encrypted. + +See the [Docker documentation](https://docs.docker.com/compose/environment-variables/set-environment-variables/#compose-file) for details about `.env` files. - - Go to the front end hosted by the Java backend [http://localhost:8080](http://localhost:8080) and login there. Requests through the front end development server [http://localhost:4200](http://localhost:4200) should then be authenticated. +*Note: User-role passwords must be BCrypt hashes encrypted with Jasypt. Follow the next step to prepare them.* - ```bash - cd iris-workstation/client/ #Navigates to the project's front end subdirectory - yarn run local #Runs the local frontend services, including any changes just made - ``` +2. Prepare user-role passwords. + 1. Create a BCrypt hash of the password. The following command uses a temporary Docker container and prints only the hash. Replace `YOUR_PASSWORD` with the password to use: + ```sh + bcrypt_hash="$(docker run --rm httpd:2.4-alpine \ + htpasswd -bnBC 12 '' 'YOUR_PASSWORD' | cut -d: -f2)" + printf '%s\n' "$bcrypt_hash" + ``` + 2. Download and unpack the [Jasypt command-line tool](http://www.jasypt.org/cli.html). If the ZIP download is unavailable, see the [Jasypt releases](https://github.com/jasypt/jasypt/releases). From the unpacked Jasypt directory, encrypt the BCrypt hash with the value assigned to `JASYPT_ENCRYPTOR_PASSWORD` in `docker/.env`: + ```sh + ./bin/encrypt.sh input="$bcrypt_hash" password="$JASYPT_ENCRYPTOR_PASSWORD" algorithm=PBEWITHHMACSHA512ANDAES_256 verbose=true stringOutputType=base64 providerName=SunJCE saltGeneratorClassName=org.jasypt.salt.RandomSaltGenerator ivGeneratorClassName=org.jasypt.iv.RandomIvGenerator + ``` + Quoting `"$bcrypt_hash"` preserves the `$` characters in the BCrypt value. + 3. Set the appropriate password variable in `docker/.env` to the encrypted output, wrapped in `ENC(...)`. For example: + ```dotenv + DEFAULT_USER_PASSWORD=ENC(123abcENCRYPTEDpass) + ``` + Repeat these steps for `DEFAULT_REVIEWER_PASSWORD` and `DEFAULT_SUPERVISOR_PASSWORD`. - 4. **(Optional)** Open a fourth terminal window and type the following commands to start up the database management container: + Default users are created only when the application initializes an empty database. If containers have already initialized the database with incorrect passwords, changing `.env` does not update those accounts. For a disposable local installation, recreate the database before starting the application: + ```sh + docker compose down + docker volume rm docker_my-db + docker compose up + ``` + This permanently deletes the local IAT database volume and its data. - **You only need this step if you want to access the database for debugging purposes.** +### Run Application via Docker +The following steps start the application in user mode. This mode runs published containers and does not include local source-code changes. To run local changes, follow the [Run Application From a Script](web/README.md#from-a-script) instructions in the `web/` subdirectory. - ```bash - cd iris-workstation/ #Navigates to the project's home directory - docker run --name iwp_php_admin --network iwp-simple_default -v phpmyadmin-volume:/etc/phpmyadmin/config.user.inc.php --link iwp_mysql:db -p 82:80 -d phpmyadmin/phpmyadmin #Runs the database container +*Note: To use a GPU for the Iris Annotation component, comment out the CPU `iwp-annotation` Docker service and uncomment the GPU service in `iat/docker/docker-compose.yml`.* -3. Use Project - 1. Open choice of web browser and navigate to `localhost:4200` - 1. Log in with username and password - 2. To see the database, open choice of web browser and navigate to `localhost:82` - 1. Log in with username and password - +The default Compose deployment exposes only the web application. ActiveMQ Artemis and MySQL remain on the internal Docker network. Use `docker-compose.local.yml` for local development; its broker and database ports bind only to `127.0.0.1`. -## NOTICE - -#### Approved for Public Release; Distribution Unlimited. Public Release Case Number 23-1989 - -#### This software (or technical data) was produced for the U. S. Government and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV (May 2014) – Alternative IV (Dec 2007) +1. In a terminal, run the following commands in the `iat/docker/` subdirectory: + ```sh + ./composeScript.sh prune # Removes unused containers, images, and other data. + docker compose up # Starts the services and user interface with Docker Compose. + ``` -#### (c) 2024 The MITRE Corporation. All Rights Reserved. +2. In a web browser, navigate to `http://localhost:8080` and log in with your configured username and password. diff --git a/acii/Dockerfile b/acii/Dockerfile new file mode 100644 index 0000000..373fa9c --- /dev/null +++ b/acii/Dockerfile @@ -0,0 +1,26 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2026 The MITRE Corporation. All Rights Reserved. +# +FROM alpine:3.23 + +COPY acii /acii +COPY buffers/src/main/proto /buffers/src/main/proto + +WORKDIR /acii/install + +# Building on some infrastructure requires the installation of a certificate authority. This is OPTIONAL +# for external systems and will only be installed when the ca_file secret is provided in the build command. +RUN --mount=type=secret,id=ca_file,required=false \ + if [ -f /run/secrets/ca_file ]; then \ + cat /run/secrets/ca_file >> /etc/ssl/certs/ca-certificates.crt; \ + fi + +RUN ./install.sh && rm -rf /acii /buffers /apache-log4cxx-* /activemq-cpp-library-* /opencv-* + +CMD [ "aciiComponent", "/etc/iwp-acii/acii.conf", "/etc/iwp-acii/logger.conf", "false", "/var/log/acii.log" ] diff --git a/acii/README.md b/acii/README.md new file mode 100644 index 0000000..ab63e8e --- /dev/null +++ b/acii/README.md @@ -0,0 +1,61 @@ +> +> NOTICE +> +> This software (or technical data) was produced for the U. S. Government and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV (May 2014) - Alternative IV (Dec 2007). +> +> (c) 2024 The MITRE Corporation. All Rights Reserved. +> + + +# ACII Component + +The ACII component is an Iris Analysis Toolkit (IAT) service that analyzes an iris image and returns its orientation, including whether the iris is left or right. + +## Starting the Service +This service can be started in two ways: +1. (Recommended) [Docker Image via the IAT](#docker-and-iat) +2. [From a Script](#from-a-script) + +If you make changes to the scripts and want to create a new Docker image, see [Rebuild Docker Image](#rebuild-docker-image). + +### Docker and IAT +This is the recommended way. +To start the service with Docker through the IAT interface, follow the [Quickstart for Users](../README.md#quickstart-for-users) instructions. + + +### From a Script +#### Prerequisites +- A Linux environment with `sudo` access. The installation script supports Alpine and APT-based distributions. +- A C++17 compiler, CMake, Make, Autotools, and Curl. +- Protocol Buffers version 3 or later, Apache ActiveMQ-CPP, Apache Log4cxx, APR, and OpenCV 2.4. +- An ActiveMQ-compatible broker reachable from the ACII service. + +The installation script installs or builds the required native dependencies and builds the ACII client. + +```sh +cd acii/install +sudo ./install.sh +``` + +#### Starting the Script +Once you have completed the prerequisites, follow these steps: +1. Start an instance of ActiveMQ and note the username and password you configure. +2. Replace `temp_value` for `artemis-user` and `artemis-password` in `acii-client/config/acii.conf` with the values from step 1. +3. Run ACII with the following command: + ```sh + /usr/local/bin/aciiComponent /etc/iwp-acii/acii.conf /etc/iwp-acii/logger.conf false /var/log/acii.log + ``` + +The ACII client is now running. It consumes messages from `iris.acii.request` and produces messages on `iris.acii.response`. + +*Note: This option is not recommended because a client must publish an `ImageServiceQuery` message to ActiveMQ on `iris.acii.request` and consume the resulting `AciiServiceResponse` message from `iris.acii.response`. A client script is not currently provided.* + +## Rebuild Docker Image +To build a new Docker image, run this command from the main `iat/` project directory: + ```sh + # Remove `--secret` argument if organization certificate does not need to be installed. + # `--platform linux/amd64` is required when building on Apple Silicon systems. + DOCKER_BUILDKIT=1 docker build --platform linux/amd64 -t new_acii_image:YOUR_TAG_HERE \ + --secret id=ca_file,src=ca_file.crt \ + -f acii/Dockerfile . + ``` diff --git a/acii/acii-client/CMakeLists.txt b/acii/acii-client/CMakeLists.txt new file mode 100755 index 0000000..d0b368d --- /dev/null +++ b/acii/acii-client/CMakeLists.txt @@ -0,0 +1,116 @@ +#[[ + NOTICE + + This software (or technical data) was produced for the U. S. Government + and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV + (May 2014) – Alternative IV (Dec 2007) + + (c) 2024 The MITRE Corporation. All Rights Reserved. +#]] + +cmake_minimum_required(VERSION 3.5) +project(acii_component) + +# Default to C++17, but allow CI to override via -DCMAKE_CXX_STANDARD=11 +if(NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + +option(EOC2_DEBUG "Build EOC2 with debug functionality" OFF) + +if(EOC2_DEBUG) + add_definitions(-DEOC2_DEBUG) +endif() + +find_package(Protobuf REQUIRED) + +# Alpine protobuf packages (v6+) are built against Abseil shared libs. +# Link Abseil DSOs explicitly to avoid "DSO missing from command line". +find_library(ABSL_CHECK_OP_LIB NAMES absl_log_internal_check_op PATHS /usr/lib /usr/local/lib) +find_library(ABSL_LOG_INTERNAL_MESSAGE_LIB NAMES absl_log_internal_message PATHS /usr/lib /usr/local/lib) +find_library(ABSL_LOG_INTERNAL_NULLGUARD_LIB NAMES absl_log_internal_nullguard PATHS /usr/lib /usr/local/lib) + +include_directories(${PROTOBUF_INCLUDE_DIRS}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +# IMPORTANT: +# Do NOT glob all protos; some of these files duplicate message definitions. +# Only list the "root" protos you want to compile. +set(ACII_PROTO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../buffers/src/main/proto") +set(ProtoFiles + "${ACII_PROTO_DIR}/Acii.proto" + "${ACII_PROTO_DIR}/Iwp.proto" +) + +foreach(p IN LISTS ProtoFiles) + if(NOT EXISTS "${p}") + message(FATAL_ERROR "Missing proto file: ${p}. ACII expects protobuf definitions from ../../buffers/src/main/proto.") + endif() +endforeach() + +protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${ProtoFiles}) + +find_package(CURL REQUIRED) +include_directories(${CURL_INCLUDE_DIR}) + +find_package(OpenCV REQUIRED) +include_directories(${OpenCV_INCLUDE_DIRS}) + +set(LOG4CXX_DIR /usr CACHE PATH "Path to Log4cxx") +file(GLOB LOG4CXX_INCLUDE ${LOG4CXX_DIR}/include/log4cxx) +include_directories(${LOG4CXX_INCLUDE}) +link_directories(${LOG4CXX_DIR}/lib) + +set(AMQCPP_DIR /usr CACHE PATH "Path to AmqCpp") +file(GLOB AMQCPP_INCLUDE ${AMQCPP_DIR}/include/activemq-cpp-*) +include_directories(${AMQCPP_INCLUDE}) +link_directories(${AMQCPP_DIR}/lib) + +include_directories(include) +file(GLOB SRC src/*) +file(GLOB HEADERS include/*) + +add_executable(aciiComponent + aciiComponent.cpp + ${SRC} ${HEADERS} + ${PROTO_SRCS} ${PROTO_HDRS} +) + +set(ACII_PROTOBUF_LIB "") +if(TARGET protobuf::libprotobuf) + set(ACII_PROTOBUF_LIB protobuf::libprotobuf) +elseif(TARGET Protobuf::libprotobuf) + set(ACII_PROTOBUF_LIB Protobuf::libprotobuf) +else() + set(ACII_PROTOBUF_LIB ${PROTOBUF_LIBRARIES}) +endif() + +target_link_libraries(aciiComponent PRIVATE + log4cxx + activemq-cpp + ${OpenCV_LIBS} + ${CURL_LIBRARIES} + ${ACII_PROTOBUF_LIB} +) + +if(ABSL_CHECK_OP_LIB) + target_link_libraries(aciiComponent PRIVATE ${ABSL_CHECK_OP_LIB}) +endif() +if(ABSL_LOG_INTERNAL_MESSAGE_LIB) + target_link_libraries(aciiComponent PRIVATE ${ABSL_LOG_INTERNAL_MESSAGE_LIB}) +endif() +if(ABSL_LOG_INTERNAL_NULLGUARD_LIB) + target_link_libraries(aciiComponent PRIVATE ${ABSL_LOG_INTERNAL_NULLGUARD_LIB}) +endif() + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/config/acii.conf DESTINATION /etc/iwp-acii) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/config/conf.ini DESTINATION /etc/iwp-acii) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/config/logger.conf DESTINATION /etc/iwp-acii) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/trainedClassifiers + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/acii/acii-client +) + +install(TARGETS aciiComponent DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) diff --git a/acii/acii-client/aciiComponent.cpp b/acii/acii-client/aciiComponent.cpp new file mode 100644 index 0000000..9872ad4 --- /dev/null +++ b/acii/acii-client/aciiComponent.cpp @@ -0,0 +1,141 @@ +// ####################################################################### +// NOTICE +// +// This software (or technical data) was produced for the U. S. Government +// and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +// (May 2014) – Alternative IV (Dec 2007) +// +// (c) 2024 The MITRE Corporation. All Rights Reserved. +// ####################################################################### + +#define _GLIBCXX_USE_NANOSLEEP 1 + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "include/Client.h" +#include "include/configparser.h" + +using std::cout; +using std::endl; +using std::map; +using std::string; +using std::transform; +using std::vector; +using std::chrono::seconds; +using std::this_thread::sleep_for; + +int main(int argc, char *argv[]) +{ + static vector required_params = {"broker-URI", + "artemis-user", + "artemis-password", + "acii-request-queue", + "acii-response-queue", + "classifier-config", + "allowed-img-exts"}; + + if (argc < 4) + { + cout << "Usage: aciiComponent"; + cout << " "; + cout << " "; + cout << " "; + // Optional. If not set, defaults to file in log4cxx config + cout << endl; + return 0; + } + + string component_config(argv[1]); + string log4cxx_config(argv[2]); + string keep_class_in_mem_str(argv[3]); + + log4cxx::PropertyConfigurator::configure(log4cxx_config); + + // Set file of log to + if (argc > 4) + { + log4cxx::LoggerPtr logger = log4cxx::Logger::getRootLogger(); + log4cxx::FileAppenderPtr appender = logger->getAppender("R"); + log4cxx::helpers::Pool p; + appender->setFile(argv[4]); + appender->activateOptions(p); + } + + log4cxx::LoggerPtr logger = + log4cxx::Logger::getLogger("acii.main"); + + + LOG4CXX_INFO(logger, "Starting aciiComponent"); + LOG4CXX_INFO(logger, "Component config: " << component_config); + LOG4CXX_INFO(logger, "Log4cxx config: " << log4cxx_config); + + ConfigParser cp; + map params; + bool success = cp.parseParams(component_config, params); + if (!success) + { + LOG4CXX_ERROR(logger, "Error loading acii component config. Exiting."); + return 1; + } + + // Check for required config parameters + for (int i = 0; i < required_params.size(); ++i) + { + if (params.find(required_params[i]) == params.end()) + { + LOG4CXX_ERROR(logger, + "Component config does not contain all required parameters"); + return 1; + } + } + + // Assume alowed img extensions are contained in comma-delimited string + char delim = ','; + vector allowed_img_extensions; + cp.parseDelimitedString( + allowed_img_extensions, params["allowed-img-exts"], delim); + + // Convert string-booleans to bool + transform(keep_class_in_mem_str.begin(), keep_class_in_mem_str.end(), + keep_class_in_mem_str.begin(), ::tolower); + bool keep_class_in_mem = (keep_class_in_mem_str == "false") ? false : true; + + activemq::library::ActiveMQCPP::initializeLibrary(); + + LOG4CXX_INFO(logger, "Creating Client"); + + Client c = Client(params["broker-URI"], + params["artemis-user"], + params["artemis-password"], + params["acii-request-queue"], + params["acii-response-queue"], + params["classifier-config"], + params["acii-output-path"], + allowed_img_extensions, + keep_class_in_mem); + + c.runRequestConsumer(); + + while (true) + { + sleep_for(seconds(1)); + } + + activemq::library::ActiveMQCPP::shutdownLibrary(); + return 0; +} diff --git a/acii/acii-client/config/acii.conf b/acii/acii-client/config/acii.conf new file mode 100755 index 0000000..1a8d172 --- /dev/null +++ b/acii/acii-client/config/acii.conf @@ -0,0 +1,19 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +broker-URI=tcp://activemq:61616 +# TODO: Need to fill this out to run this locally +artemis-user=temp_value +artemis-password=temp_value +acii-request-queue=iris.acii.request +acii-response-queue=iris.acii.response +classifier-config=/etc/iwp-acii/conf.ini +allowed-img-exts=PNG,JP2,BMP,PIXMAP +acii-output-path=/tmp/ diff --git a/acii/acii-client/config/conf.ini b/acii/acii-client/config/conf.ini new file mode 100755 index 0000000..648b414 --- /dev/null +++ b/acii/acii-client/config/conf.ini @@ -0,0 +1,26 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +##################################################################### +# Classifier section + +RBF gamma used in training = 0.5 +XML filename for upright / upside-down classifier = /usr/local/share/acii/acii-client/trainedClassifiers/SVM_UD_ALL.xml +XML filename for left / right classifier = /usr/local/share/acii/acii-client/trainedClassifiers/SVM_LR_ALL.xml +XML filename for upright / upside-down feature normalization coefficients = /usr/local/share/acii/acii-client/trainedClassifiers/FC_UD_ALL.xml +XML filename for left / right feature normalization coefficients = /usr/local/share/acii/acii-client/trainedClassifiers/FC_LR_ALL.xml + +##################################################################### +# IRIS IMAGE SEGMENTATION PARAMETERS + +Minimum diameter for pupil = 48 +Maximum diameter for pupil = 160 +Minimum diameter for iris = 180 +Maximum diameter for iris = 320 diff --git a/acii/acii-client/config/logger.conf b/acii/acii-client/config/logger.conf new file mode 100644 index 0000000..198fde4 --- /dev/null +++ b/acii/acii-client/config/logger.conf @@ -0,0 +1,29 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +log4j.rootLogger=debug, stdout, R + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +# Pattern to output the caller's file name and line number. +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-40.40c{6}:%m%n + +log4j.appender.R=org.apache.log4j.RollingFileAppender +log4j.appender.R.File=logs/acii.log + +log4j.appender.R.MaxFileSize=100MB +# Keep one backup file +log4j.appender.R.MaxBackupIndex=1 + +log4j.appender.R.layout=org.apache.log4j.PatternLayout +log4j.appender.R.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-40.40c{6}:%m%n + +log4j.logger.acii = DEBUG diff --git a/acii/acii-client/include/Client.h b/acii/acii-client/include/Client.h new file mode 100755 index 0000000..aeabb15 --- /dev/null +++ b/acii/acii-client/include/Client.h @@ -0,0 +1,110 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +#ifndef ACII_CLIENT_INCLUDE_CLIENT_H_ +#define ACII_CLIENT_INCLUDE_CLIENT_H_ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "EOC2Manager.h" + +using activemq::transport::DefaultTransportListener; +using cms::BytesMessage; +using cms::CMSException; +using cms::Connection; +using cms::DeliveryMode; +using cms::Destination; +using cms::ExceptionListener; +using cms::Message; +using cms::MessageConsumer; +using cms::MessageListener; +using cms::MessageProducer; +using cms::Session; +using EOC2::EOC2Manager; +using std::string; +using std::vector; + +class Client : public MessageListener, public ExceptionListener, public DefaultTransportListener +{ + +private: + Session *session; + Connection *connection; + Destination *request_dest; + Destination *response_dest; + MessageConsumer *consumer; + MessageProducer *producer; + string broker_uri; + string artemis_user; + string artemis_password; + string requests_dest_name; + string response_dest_name; + string output_path; + EOC2Manager *manager; + vector allowed_img_extensions; + + Client &operator=(const Client &); + void cleanup(); + string getImage(string id, string url, + const string bytes); + +public: + ~Client(); + Client(const string &broker_uri, + const string &artemis_user, + const string &artemis_password, + const string &requests_dest_name, + const string &response_dest_name, + string &eye_class_config_path, + const string &output_path, + vector allowed_img_extensions, + bool keep_class_in_mem = true); + + Client(const string &broker_uri, + const string &artemis_user, + const string &artemis_password, + const string &requests_dest_name, + const string &response_dest_name, + string &eye_class_config_path, + vector allowed_img_extensions, + bool keep_class_in_mem = true); + + void runRequestConsumer(); + void onMessage(const Message *message); + void onException(const CMSException &ex); + void transportInterrupted(); + void transportResumed(); + bool imgAllowed(string &imgExt); + static size_t write_data(void *contents, size_t size, size_t nmemb, + FILE *stream); +}; + +#endif // ACII_CLIENT_INCLUDE_CLIENT_H_ diff --git a/acii/acii-client/include/EOC2Eye.h b/acii/acii-client/include/EOC2Eye.h new file mode 100755 index 0000000..d5e8df2 --- /dev/null +++ b/acii/acii-client/include/EOC2Eye.h @@ -0,0 +1,258 @@ +/***************************************************************************** +* Eye Orientation CLassifier v2 (U/D + L/R) +* Author: Adam Czajka, aczajka@nd.edu, May 2016 +* +* Code in this file is partially based on Open Source for Iris (OSIRIS) v. 4.1 +* under BSD license http://opensource.org/licenses/bsd-license.php +******************************************************************************/ + +#ifndef ACII_CLIENT_INCLUDE_EOC2EYE_H_ +#define ACII_CLIENT_INCLUDE_EOC2EYE_H_ + +#ifdef EOC2_DEBUG +#include +#endif +#include +#include +#include "EOC2Utils.h" +#include "Iwp.pb.h" + +using std::string; +using std::vector; +using org::mitre::iwp::buffers::ImageServiceQuery; + +namespace EOC2 { +/** Eye handler. + * Allows to process one eye, that is calculate + * L/R and U/D features. This class is the link between the manager + * and the processing functions. + * @see EOC2Processings + * @see EOC2Manager + */ +class EOC2Eye { + public : + /** Default constructor. + * Initialize all pointers of image to 0. + * Initialize pupil and iris circles to (0,0,0). + */ + EOC2Eye(int image_type = ImageServiceQuery::PNG, + int image_width = 0, int image_height = 0); + + /** Default destructor. + * Release all images (free memory). + */ + ~EOC2Eye(); + + /** Load the original image corresponding to the eye. + * @param rFilename Complete path of the image + * @return void + * @see loadImage() + */ + void loadOriginalImage(const string &rFilename); + + /** + * Loads the eye image from a raw pixel format (8 bpp 3 channel) + * + * @param rFilename The local path to the file + */ + void loadPixmap(const string &rFilename); + + /** Initialize the mask. + * Create the mask and set all pixels to 255. + * This function is used when user did not provide the mask + * or did not ask to compute the mask and yet mask is required to go on. + * @return void + */ + void initMask(); + + + /** Segment the eye. + * Initialize the mask if they have not been created yet.\n + * Call the function EOC2Processings::segment().\n + * Draw the results of segmentation on the private + * attribute mpSegmentedImage (color image). + * @param minIrisDiameter The minimum diameter for segmenting the iris + * @param minPupilDiameter The minimum diameter for segmenting the pupil + * @param maxIrisDiameter The maximum diameter for segmenting the iris + * @param maxPupilDiameter The maximum diameter for segmenting the pupil + * @return void + * @see EOC2Processings::segment() + */ + void segment(int minIrisDiameter, int minPupilDiameter, int maxIrisDiameter, + int maxPupilDiameter); + + + /** Normalize image and mask. + * If the mask is not already initialized, the function does intialize it + * to 255. Uses the Daugman's rubber-sheet method. + * @param widthOfNormalizedIris Width of normalized image + * @param heightOfNormalizedIris Height of normalized image + * @return void + * @see EOC2Processings::normalize() + */ + void normalize(int widthOfNormalizedIris, int heightOfNormalizedIris); + + + /** Calculates the image features required for upright / upside-down + * classification. + * @return void + */ + void featuresUD(); + + + /** Calculates the image features required for left / right classification. + * @return void + */ + void featuresLR(); + + +#ifdef EOC2_DEBUG + /** Prints features calulated for upright / upside-down classification. + * @return void + */ + void printFeaturesUD(int &iFileNo, const string &rFilename, + const string &label); + + + /** Prints the feature header for upright / upside-down classification. + * @return void + */ + void printFeaturesUDHeader(); + + + /** Prints features calulated for left / right classification. + * @return void + */ + void printFeaturesLR(int &iFileNo, const string &rFilename, + const string &label); + + + /** Prints the feature header for left / right classification. + * @return void + */ + void printFeaturesLRHeader(); +#endif + + + /** Returns the number of features used in left / right classification. + * @return int + */ + int getNumberOfLRfeatures(); + + + /** Returns the number of features used in upright / upside-down classification. + * @return int + */ + int getNumberOfUDfeatures(); + + + /** Image features for upright / upside-down classification. */ + struct _stFeaturesUD { + float fVerticalPupilShift; + float fEyelidCoverage; + float fEyelidValue; + float fEyelidFiltered; + float fEyelidFilteredCosVar; + float fEyelidFilteredSinVar; + } stFeaturesUD; + + + /** Image features for left / right classification. */ + struct _stFeaturesLR { + float fEyelidValue; + float fHorizontalPupilShift; + } stFeaturesLR; + + + /** Flag indicating that the iris image was correctly processed. */ + bool bProcessedOK; + + +#ifdef EOC2_DEBUG + /** Save debug data (not used in the final version): + * - segmented color image + * - binary mask + * - normalized image + * - normalized mask + * - contour parameters + * @param rFilename Complete path of the image + * @return void + * @see saveImage() + */ + void saveSegmentedImage ( const string &rFilename ) ; + void saveMask ( const string &rFilename ) ; + void saveNormalizedImage ( const string &rFilename ) ; + void saveNormalizedMask ( const string &rFilename ) ; + void saveParameters ( const string &rFilename ) ; +#endif + + private : + + /** The format of the image being loaded */ + int imageType; + + /** The height & width (for pixmap images) */ + int imageWidth; + int imageHeight; + + /** The original image corresponding to the eye (input only). */ + IplImage *mpOriginalImage; + +#ifdef EOC2_DEBUG + /** The segmented image (color) corresponding to the eye (output only). */ + IplImage * mpSegmentedImage ; +#endif + + /** The mask corresponding to the eye (input and/or output). */ + IplImage * mpMask; + + /** The normalized image corresponding to the eye (input and/or output). */ + IplImage * mpNormalizedImage; + + /** The normalized mask corresponding to the eye (input and/or output). */ + IplImage * mpNormalizedMask; + + /** The pupil circle corresponding to the eye (input and/or output). */ + EOC2Circle mPupil; + + /** The iris circle corresponding to the eye (input and/or output). */ + EOC2Circle mIris; + + /** The pupil coarse contour corresponding to the eye. */ + vector mCoarsePupilContour; + + /** The iris coarse contour corresponding to the eye. */ + vector mCoarseIrisContour; + + /** The theta sampling for pupil coarse contour. */ + vector mThetaCoarsePupil; + + /** The theta sampling for iris coarse contour. */ + vector mThetaCoarseIris; + + /** Number of features used in upright / upside-down recognition. */ + int iNumberOfUDfeatures; + + /** Number of features used in left / right recognition. */ + int iNumberOfLRfeatures; + + /** Generic function to save the image-like attributes of the eye. + * @param rFilename The complete path of the image + * @param ppImage A pointer of pointer on the image + * @return void + */ + void loadImage(const string &rFilename, IplImage ** ppImage); + +#ifdef EOC2_DEBUG + /** Generic function to load the image-like attributes of the eye. + * Needed only when saving the debug data (not used in the final version) + * @param rFilename The complete path of the image + * @param pImage A pointer on the image + * @return void + */ + void saveImage(const string &rFilename, const IplImage *pImage); +#endif +}; // class EOC2Eye +} // namespace EOC2 + +#endif // ACII_CLIENT_INCLUDE_EOC2EYE_H_ diff --git a/acii/acii-client/include/EOC2Manager.h b/acii/acii-client/include/EOC2Manager.h new file mode 100755 index 0000000..db1759b --- /dev/null +++ b/acii/acii-client/include/EOC2Manager.h @@ -0,0 +1,200 @@ +/***************************************************************************** +* Eye Orientation CLassifier v2(U/D + L/R) +* Author: Adam Czajka, aczajka@nd.edu, May 2016 +* +* Code in this file is partially based on Open Source for Iris(OSIRIS) v. 4.1 +* under BSD license http://opensource.org/licenses/bsd-license.php +******************************************************************************/ + + +#ifndef ACII_CLIENT_INCLUDE_EOC2MANAGER_H_ +#define ACII_CLIENT_INCLUDE_EOC2MANAGER_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "EOC2Eye.h" + +using std::map; +using std::string; + +namespace EOC2 { + +/** Overall manager. + * This class manages all the files, configuration, saving + * and loading options. It uses EOC2Eye to execute technical processings. + * @see EOC2Eye + */ +class EOC2Manager { + public: + /** Default constructor. + * Associate lines of configuration file to the attributes of the class. + * Initialize all parameters to default values. + * @see initConfiguration() + */ + EOC2Manager(bool keep_class_in_mem = true); + + /** Default destructor. + * Release matrix containing the application points.\n + */ + ~EOC2Manager(); + + /** Load configuration from a text file. + * @param rFilename The relative path to the filename(relative to the + * executable) + * @return void + * @see showConfiguration() + */ + void loadConfiguration(const string &rFilename = "conf.ini"); + + /** Show configuration in prompt command. + * @see initConfiguration() + * @see loadConfiguration() + */ + void showConfiguration(); + + /** Check configuration provided in conf.ini file . + * @return int: 1 configuration correct, 0 otherwise + * @see initConfiguration() + * @see loadConfiguration() + */ + int checkConfiguration(); + + /** Run the software according to the configuration. + * Build the eye lists and process them as requested by the configuration file + * @see processUD() + */ + void run(string image_path, int &up_down, int &left_right, int image_type, + int image_width = 0, int image_height = 0); + + + private: + log4cxx::LoggerPtr logger; + + // If true, keeps classification data structures in memory, + // else loads each processing call + bool keep_class_in_mem; + + // Classification data-structures + CvSVM* SVM_UD; + cv::Mat* colSTD_UD; + cv::Mat* colMEAN_UD; + CvSVM* SVM_LR; + cv::Mat* colSTD_LR; + cv::Mat* colMEAN_LR; + + // Classification file paths + string mClassifierFileNameUD; + string mClassifierFileNameLR; + string mFeatureNormFileNameUD; + string mFeatureNormFileNameLR; + + // Parameters + int mMinPupilDiameter; + int mMaxPupilDiameter; + int mMinIrisDiameter; + int mMaxIrisDiameter; + int mWidthOfNormalizedIris; + int mHeightOfNormalizedIris; + float mRBFgamma; + + // Maps to associate a string(conf file) to a variable(not the value of the + // variable !) + map mMapBool; + map mMapInt; + map mMapString; + map mMapFloat; + + void loadClassifier_UD(); + void loadClassifier_LR(); + void cleanupClassifier_UD(); + void cleanupClassifier_LR(); + void cleanupClassifier(); + + /** Loads classification data structures from disk. Read from + * mClassifierFileNameUD, mClassifierFileNameLR, mFeatureNormFileNameUD and + * mFeatureNormFileNameUD as loaded from + * config file in loadConfiguration(). Called after loadConfiguration(); + * @see loadConfiguration() + */ + void loadClassifier(); + + /** Initialize all configuration options to default values. + * Default values are : + * - For all directory/textfile paths : "" + * - Minimum and maximum diameter for the pupil : 21 - 91 pixels + * - Minimum and maximum diameter for the iris : 99 - 399 pixels + * - All commands of processing are set to false => nothing is going to be + * executed + * @see loadConfiguration() + * @see showConfiguration() + */ + void initConfiguration(); + + /** Calculate the image features required for upright / upside-down classification. + * + * @param rName The eye name + * @param rEye The eye to be processed + * @return void + * @see EOC2Eye + */ + void processUD(const string &rFileName, EOC2Eye &rEye); + + /** Use of the trained SVM classifier for recognition of upright / upside-down + * orientation. Returns 0 if upright, 1 if upside-down, -1 on error + * + * @param vListOfTestingImages List of images used in training + * @return int + */ + int recognitionUD(string image_path, int image_type, int image_width = 0, + int image_height = 0); + + + /** Calculate the image features required for left / right classification. + * @param rName The eye name + * @param rEye The eye to be processed + * @return void + * @see EOC2Eye + */ + void processLR(const string &rFileName, EOC2Eye &rEye); + + + /** Use of the trained SVM classifier for left / right classification. + * Returns 0 if left, 1 if right, -1 on error + * @param vListOfTestingImages List of images used in training + * @return int + */ + int recognitionLR(string image_path, int image_type, int image_width = 0, + int image_height = 0); + + +#ifdef EOC2_DEBUG + // Private variables needed if debug data is saved + // Suffix for filenames + string mSuffixSegmentedImages; + string mSuffixParameters; + string mSuffixMasks; + string mSuffixNormalizedImages; + string mSuffixNormalizedMasks; + // Outputs + string mOutputDirSegmentedImages; + string mOutputDirParameters; + string mOutputDirMasks; + string mOutputDirNormalizedImages; + string mOutputDirNormalizedMasks; +#endif +}; // class EOC2Manager +} // namespace EOC2 + +#endif // ACII_CLIENT_INCLUDE_EOC2MANAGER_H_ + diff --git a/acii/acii-client/include/EOC2Processings.h b/acii/acii-client/include/EOC2Processings.h new file mode 100755 index 0000000..93a8597 --- /dev/null +++ b/acii/acii-client/include/EOC2Processings.h @@ -0,0 +1,271 @@ +/***************************************************************************** +* Eye Orientation CLassifier v2(U/D + L/R) +* Author: Adam Czajka, aczajka@nd.edu, May 2016 +* +* Code in this file is based on Open Source for Iris(OSIRIS) v. 4.1 +* under BSD license http://opensource.org/licenses/bsd-license.php +******************************************************************************/ + + +#ifndef ACII_CLIENT_INCLUDE_EOC2PROCESSINGS_H_ +#define ACII_CLIENT_INCLUDE_EOC2PROCESSINGS_H_ + +#define OSI_PI 3.14159f + +// Sizes for iris and pupil +#define OSI_SMALLEST_PUPIL 11 +#define OSI_SMALLEST_IRIS 99 +#define OSI_MAX_RATIO_PUPIL_IRIS 0.7f +#define OSI_MIN_RATIO_PUPIL_IRIS 0.2f + +#include +#include + +#include +#include + +#include "EOC2Utils.h" + +using std::string; +using std::vector; + +namespace EOC2 { + +/** Iris segmentation functions. + * Public functions are the main steps for iris segmentation and normalization. + * Private functions are used in the public functions. + */ +class EOC2Processings { + public: + /** Default constructor. */ + EOC2Processings(); + + /** Default destructor. */ + ~EOC2Processings(); + + /** Find inner and outer boundaries of iris. + * Detect and locate the pupil. Smooth the image using the Anisotropic + * Smoothing. + * Extract the radial gradients using Sobel operator. + * Find 4 optimal contours(accurate and coarse contours for pupil and iris) + * using the Viterbi algorithm. + * Build the binary mask of iris : each pixel is labelled as "iris"(1) or + * "not iris"(0). + * Compute the normalization from the coarse contours detected by the Viterbi. + * The four optional parameters allow adaptation to different database : \n + * Increase the minimum diameters and/or decrease the maximum diameters : + * faster, but can miss the pupil/iris that are not in the size range \n + * Decrease the minimum diameters and/or increase the maximum diameters : + * increase the probability of false alarm + * @param [in] pSrc An eye image + * @param [out] pMask The mask of iris. Filled by the function segment. + * Must be created before the function segment + * @param [out] rPupil The circle for the pupil. Filled by the function + * segment + * @param [out] rIris The circle for the iris. Filled by the function segment + * @param [in] minIrisDiameter The minimum diameter for segmenting the iris + * @param [in] minPupilDiameter The minimum diameter for segmenting the pupil + * @param [in] maxIrisDiameter The maximum diameter for segmenting the iris + * @param [in] maxPupilDiameter The maximum diameter for segmenting the pupil + * @return void + * @see detectPupil(), findContour(), EOC2Circle::computeCircleFitting(), + * normalize(), EOC2Eye::segment() + */ + void segment(const IplImage * pSrc, + IplImage * pMask, + EOC2Circle & rPupil, + EOC2Circle & rIris, + vector & rThetaCoarsePupil, + vector & rThetaCoarseIris, + vector & rCoarsePupilContour, + vector & rCoarseIrisContour, + int minIrisDiameter = OSI_SMALLEST_IRIS, + int minPupilDiameter = OSI_SMALLEST_PUPIL, + int maxIrisDiameter = 0, + int maxPupilDiameter = 0); + + + + /** Normalize iris by Daugman's rubber sheet method. + * Use the function segment() to obtain pupil and iris contours. + * @param pSrc The source image + * @param pDst The normalized image. Must be created BEFORE this function. + * @param rPupil The circle modeling the pupil + * @param rIris The circle modeling the iris + * @return void + * @see normalizeFromContour(), segment(), encode(), EOC2Eye::normalize() + */ + void normalize(const IplImage *pSrc, IplImage *pDst, const EOC2Circle &rPupil, + const EOC2Circle &rIris); + + + + /** Normalize iris by Daugman's rubber sheet method. + * Use the function segment() to obtain pupil and iris contours. + * @param pSrc The source image + * @param pDst The normalized image. Must be created BEFORE this function. + * @param rPupil The contour modeling the pupil + * @param rIris The contour modeling the iris + * @return void + * @see normalize(), segment(), encode(), EOC2Eye::normalize() + */ + void normalizeFromContour(const IplImage *pSrc, + IplImage *pDst, + const EOC2Circle &rPupil, + const EOC2Circle &rIris, + const vector rThetaCoarsePupil, + const vector rThetaCoarseIris, + const vector &rPupilCoarseContour, + const vector &rIrisCoarseContour); + + + CvPoint interpolate(const vector coarseContour, + const vector coarseTheta, + const float theta); + + + private : + + /** Add borders on left and right of unwrapped image. + * @param pImage The original image + * @param width The border width + * @return The new image + * @see match(), encode() + */ + IplImage * addBorders(const IplImage *pImage, int width); + + + /** Convert polar coordinates to cartesian coordinates. + * @param rCenter The reference center in cartesian coordinates + * @param rRadius The radius coordinate + * @param rTheta The angle coordinate in radians + * @return The cartesian coordinates of the point + */ + CvPoint convertPolarToCartesian(const CvPoint &rCenter, int rRadius, + float rTheta); + + + /** Morphological reconstruction. + * @param pMarker [in] A binary image. The "on" pixels are to be reconstructed + * @param pMask [in] A binary image. The "off" pixels will not be + * reconstructed + * @param pDst [out] The destination image. Must be created BEFORE this + * function. + * @return void + * @see fillWhiteHoles() + */ + void reconstructMarkerByMask(const IplImage *pMarker, const IplImage *pMask, + IplImage *pDst); + + + /** Fill white holes such as spotlights(specular reflections). + * @param pSrc [in] The source image + * @param pDst [out] The destination image + * @return void + * @see reconstructMarkerByMask() + */ + void fillWhiteHoles(const IplImage *pSrc, IplImage *pDst); + + + /** Detect a pupil inside an image. + * The optional arguments limit the search domain. + * @param pSrc [in] The source image + * @param rPupil [out] The detected pupil + * @param minPupilDiameter [in] The minimum diameter for detecting the pupil + * @param maxPupilDiameter [in] The maximum diameter for detecting the pupil + * @return void + * @see segment(), fillWhiteHoles() + */ + void detectPupil(const IplImage *pSrc, EOC2Circle &rPupil, + int minPupilDiameter = OSI_SMALLEST_PUPIL, + int maxPupilDiameter = 0); + + + /** Show an image(rescale if needed). + * @param pImage An image + * @param delay Milliseconds to wait + * @param rWindowName The window name + * @return void + */ + void showImage(const IplImage *pImage, int delay = 0, + const string &rWindowName = "Show image"); + + + /** Unwrap a ring into a rectangular band. + * @param pSrc The source image + * @param rCenter The ring center + * @param minRadius The minimum radius + * @param maxRadius The maximum radius + * @param rTheta A vector of angles in radians + * @return The unwrapped ring + * @see findContour(), runViterbi(), convertPolarToCartesian() + */ + IplImage *unwrapRing(const IplImage *pSrc, const CvPoint &rCenter, + int minRadius, int maxRadius, + const vector &rTheta); + + + /** Smooth image using anisotropic smoothing. + * @param pSrc [in] The source image + * @param pDst [out] The destination image + * @param iterations [in] The number of iterations + * @param lambda [in] The smooth constraint + * @return void + * @see segment() + */ + void processAnisotropicSmoothing(const IplImage *pSrc, IplImage *pDst, + int iterations = 100, float lambda = 1); + + + /** Compute vertical gradients using Sobel operator. + * @param pSrc [in] The source image + * @param pDst [out] The destination image. Must be IPL_DEPTH_32F + * @return void + * @see findContour() + */ + void computeVerticalGradients(const IplImage *pSrc, IplImage *pDst); + + + /** Run Viterbi algorithm on gradient or probability image and find the optimal path. + * This function is used by findContour function. \n + * Source image is an \b unwrapped image. This function works in polar representation. + * @param pSrc An unwrapped image + * @param rOptimalPath The series of radii + * @return void + * @see unwrapRing() + * @see convertPolarToCartesian() + * @see findContour() + */ + void runViterbi(const IplImage *pSrc, vector &rOptimalPath); + + + /** Find a contour using anisotropic smoothing + viterbi algorithm. + * @param pSrc The source image + * @param rCenter The center around which the contour turns + * @param rTheta A vector of angles in radians + * @param minRadius The minimum radius for the contour + * @param maxRadius The maximum radius for the contour + * @param pMask An optional mask to forbid some pixels during the search of the contour + * @return The contour + * @see runViterbi(), unwrapRing() + */ + vector findContour(const IplImage *pSrc, const CvPoint &rCenter, + const vector &rTheta, + int minRadius, int maxRadius, + const IplImage *pMask = 0); + + + /** Draw a contour(vector of CvPoint) on an image. + * @param pImage The image on which contour is drawn + * @param rContour The contour to draw + * @param rColor The color of contour + * @param thickness The contour thickness + * @return void + */ + void drawContour(IplImage *pImage, const vector &rContour, + const CvScalar &rColor = cvScalar(255), int thickness = 1); +}; // class EOC2Processings +} // namespace EOC2 + +#endif // ACII_CLIENT_INCLUDE_EOC2PROCESSINGS_H_ + diff --git a/acii/acii-client/include/EOC2Utils.h b/acii/acii-client/include/EOC2Utils.h new file mode 100755 index 0000000..bd538e5 --- /dev/null +++ b/acii/acii-client/include/EOC2Utils.h @@ -0,0 +1,258 @@ +/***************************************************************************** +* Eye Orientation CLassifier v2 (U/D + L/R) +* Author: Adam Czajka, aczajka@nd.edu, May 2016 +* +* Code in this file is partially based on Open Source for Iris (OSIRIS) v. 4.1 +* under BSD license http://opensource.org/licenses/bsd-license.php +******************************************************************************/ + +#pragma once + +#ifndef ACII_CLIENT_INCLUDE_EOC2UTILS_H_ +#define ACII_CLIENT_INCLUDE_EOC2UTILS_H_ + +#include +#include + +#include +#include +#include +#include // for lowercase/uppercase conversions +#include +#include + +using std::string; +using std::vector; +using std::ostringstream; +using std::transform; +using std::istringstream; +using std::runtime_error; + +namespace EOC2 { + +class EOC2StringUtils { + public: + /** Default constructor. */ + EOC2StringUtils() { + // Do nothing + } + + /** Default destructor. */ + ~EOC2StringUtils() { + // Do nothing + } + + + /** Convert any basic type into string. + * @param rData The data to convert + * @return A string + */ + template < typename T > string toString(const T &rData) { + ostringstream oss; + oss << rData; + return oss.str(); + } + + /** Convert a string into any basic type. + * @param rString A string + * @return Template T + */ + template < typename T > T fromString(const string &rString); + + + /** Remove leading and trailing spaces and/or tabs. + * @param rString A string + * @return A string + */ + string trim(const string &rString) { + string s = rString.substr(0, rString.find_last_not_of("\r\n") + 1); + int first = s.find_first_not_of(" \t"); + if (first != string::npos) + return s.substr(first, s.find_last_not_of(" \t") - first + 1); + else + return ""; + } + + + /** Convert BackSlashes into slashes + * @param rString A string + * @return A string + */ + string convertSlashes(const string &rString) { + string out = rString; + + for (int i = 0; i < static_cast(rString.size()); i++) { + if (rString[i] == '\\') { + out[i] = '/'; + } + } + return out; + } + + + /** Convert to uppercase. + * @param rString A string + * @return A string + */ + string toUpper(const string &rString) { + string out = rString; + transform(out.begin(), out.end(), out.begin(), ::toupper); + return out; + } + + /** Convert to lowercase. + * @param rString A string + * @return A string + */ + string toLower(const string &rString) { + string out = rString; + transform(out.begin(), out.end(), out.begin(), ::tolower); + return out; + } + + /** Extract the filename from a full path string + * @param rFullPath A string + * @return A string + */ + string extractFileName(const string &rFullPath) { + string filename = rFullPath; + int pos = filename.find_last_of("/"); + if (pos != string::npos) filename = filename.substr(pos); + return filename.substr(0, filename.find_last_of(".")); + } +}; // class EOC2StringUtils + + + +// Definition of fromString +template < typename T > +T EOC2StringUtils::fromString(const string &rString) { + istringstream iss(rString); + T out; + if (!(iss >> out)) { + throw runtime_error( + "Cannot convert " + rString + " into basic type"); + } + return out; +} + + +// Specialization of function fromString() for boolean +template < > +inline bool EOC2StringUtils::fromString(const string &rString) { + string s = trim(toLower(rString)); + if (s == "yes" || s == "true" || s == "on" || s == "y" || s == "1") + return true; + else if (s == "no" || s == "false" || s == "off" || s == "n" || s == "0") + return false; + else + throw runtime_error("Cannot convert " + rString + " into boolean"); +} + + +/** Circle handler. + * Used by the Daugman's rubber sheet method + * @see EOC2Processings::normalize() + */ +class EOC2Circle { + public: + /** Default constructor. */ + EOC2Circle(); + + /** Default destructor. */ + ~EOC2Circle(); + + + /** Overloaded constructor. + * @param rCenter Initialization of the center + * @param rRadius Initialization of the radius + */ + EOC2Circle(const CvPoint &rCenter, int rRadius); + EOC2Circle(const CvPoint2D32f &fCenter, float fRadius); + + + /** Compute circle fitting by least-squares method. + * This function is called by EOC2Processings::segment() \n + * Reference : + * http://www.dtcenter.org/met/users/docs/write_ups/circle_fit.pdf + * @param rPoints A contour in cartesian coordinates + * @return void + * @see segment() + */ + // void computeCircleFitting(const vector &rPoints); + void computeCircleFittingFloat(const vector &rPoints); + + + /** Draw a circle on an image. + * @param pImage The image on which circle is to be drawn + * @param rColor Color of the circle + * @param thickness Circle thickness. Set to -1 to draw the disk inside the + * circle + * @return void + */ + void drawCircle(IplImage *pImage, const CvScalar &rColor = cvScalar(255), + int thickness = 1); + + + /** Get the circle center. + * @return The circle center + */ + CvPoint getCenter() const; + CvPoint2D32f getCenterFloat() const; + + + /** Get the circle radius. + * @return The circle radius + */ + int getRadius() const; + float getRadiusFloat() const; + + + /** Set the circle center. + * @param rCenter The circle center + * @return void + */ + void setCenter(const CvPoint &rCenter); + void setCenter(const CvPoint2D32f &fCenter); + + + /** Set the circle radius. + * @param rRadius The circle radius + * @return void + */ + void setRadius(int rRadius); + void setRadius(float fRadius); + + + /** Set the circle center and radius. + * @param rCenter The circle center + * @param rRadius The circle radius + * @return void + */ + void setCircle(const CvPoint &rCenter, int rRadius); + void setCircle(const CvPoint2D32f &fCenter, float fRadius); + + + /** Set the circle center and radius. + * @param rCenterX The x-coordinate of circle center + * @param rCenterY The y-coordinate of circle center + * @param rRadius The circle radius + * @return void + */ + void setCircle(int rCenterX, int rCenterY, int rRadius); + void setCircle(float rCenterX, float rCenterY, float rRadius); + + + private: + /** The circle center. */ + CvPoint mCenter; + CvPoint2D32f fCenter; + + /** The circle radius. */ + int mRadius; + float fRadius; +}; // class EOC2Circle +} // namespace EOC2 + + +#endif // ACII_CLIENT_INCLUDE_EOC2UTILS_H_ diff --git a/acii/acii-client/include/configparser.h b/acii/acii-client/include/configparser.h new file mode 100755 index 0000000..ef99a2e --- /dev/null +++ b/acii/acii-client/include/configparser.h @@ -0,0 +1,34 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +#ifndef ACII_CLIENT_INCLUDE_CONFIGPARSER_H_ +#define ACII_CLIENT_INCLUDE_CONFIGPARSER_H_ + +#include +#include +#include + +using std::map; +using std::string; +using std::vector; + +class ConfigParser +{ +public: + ConfigParser(); + + void strip(string &s); + + bool parseParams(string &config_file, map ¶ms); + + void parseDelimitedString(vector &delimited, string &s, char delim); +}; + +#endif // ACII_CLIENT_INCLUDE_CONFIGPARSER_H_ diff --git a/acii/acii-client/src/Client.cpp b/acii/acii-client/src/Client.cpp new file mode 100755 index 0000000..48d2f0c --- /dev/null +++ b/acii/acii-client/src/Client.cpp @@ -0,0 +1,474 @@ +// ####################################################################### +// NOTICE +// +// This software (or technical data) was produced for the U. S. Government +// and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +// (May 2014) – Alternative IV (Dec 2007) +// +// (c) 2024 The MITRE Corporation. All Rights Reserved. +// ####################################################################### + +#include "Client.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Acii.pb.h" +#include "Iwp.pb.h" + +using activemq::core::ActiveMQConnectionFactory; +using log4cxx::Logger; +using org::mitre::iwp::buffers::AciiServiceResponse; +using org::mitre::iwp::buffers::HorizontalAlignment; +using org::mitre::iwp::buffers::ImageServiceQuery; +using org::mitre::iwp::buffers::VerticalAlignment; +using std::exception; +using std::find; +using std::string; +using std::transform; +using std::unique_ptr; +using std::vector; + +const extern log4cxx::LoggerPtr logger = + Logger::getLogger("acii.client"); + +#define CURL_STATICLIB + +void Client::cleanup() +{ + try + { + if (connection != NULL) + { + LOG4CXX_INFO(logger, "Connection found, closing the existing conneciton!"); + connection->close(); + } + } + catch (CMSException &e) + { + LOG4CXX_ERROR(logger, "Error while closing connection: " << e.what()); + e.printStackTrace(); + } + + delete request_dest; + request_dest = NULL; + delete response_dest; + response_dest = NULL; + delete consumer; + consumer = NULL; + delete producer; + producer = NULL; + delete session; + session = NULL; + delete connection; + connection = NULL; + delete manager; + manager = NULL; + + LOG4CXX_INFO(logger, "Connection resources destroyed"); +} + +Client::~Client() { this->cleanup(); } + +Client::Client(const string &broker_uri, + const string &artemis_user, + const string &artemis_password, + const string &requests_dest_name, + const string &response_dest_name, + string &eye_class_config_path, + const string &output_path, + vector allowed_img_extensions, + bool keep_class_in_mem) +{ + + this->connection = NULL; + this->session = NULL; + this->request_dest = NULL; + this->response_dest = NULL; + this->consumer = NULL; + this->producer = NULL; + this->broker_uri = broker_uri; + this->artemis_user = artemis_user; + this->artemis_password = artemis_password; + this->requests_dest_name = requests_dest_name; + this->response_dest_name = response_dest_name; + this->output_path = output_path; + this->allowed_img_extensions = allowed_img_extensions; + + try + { + manager = new EOC2Manager(keep_class_in_mem); + manager->loadConfiguration(eye_class_config_path); + manager->showConfiguration(); + } + catch (exception &e) + { + LOG4CXX_ERROR(logger, "Error while loading config: " << e.what() << ". Exiting."); + exit(1); + } + catch (...) + { + LOG4CXX_ERROR(logger, "Error while loading config. Exiting."); + exit(1); + } +} + +// Use this for iris-workstation integration +Client::Client(const string &broker_uri, + const string &artemis_user, + const string &artemis_password, + const string &requests_dest_name, + const string &response_dest_name, + string &eye_class_config_path, + vector allowed_img_extensions, + bool keep_class_in_mem) +{ + + GOOGLE_PROTOBUF_VERIFY_VERSION; + + this->broker_uri = broker_uri; + this->artemis_user = artemis_user; + this->artemis_password = artemis_password; + this->requests_dest_name = requests_dest_name; + this->response_dest_name = response_dest_name; + this->allowed_img_extensions = allowed_img_extensions; + + this->connection = NULL; + this->session = NULL; + this->request_dest = NULL; + this->consumer = NULL; + this->producer = NULL; + + try + { + manager = new EOC2Manager(keep_class_in_mem); + manager->loadConfiguration(eye_class_config_path); + manager->showConfiguration(); + } + catch (exception &e) + { + LOG4CXX_ERROR(logger, "Error while loading config: " << e.what() << ". Exiting."); + exit(1); + } + catch (...) + { + LOG4CXX_ERROR(logger, "Error while loading config. Exiting."); + exit(1); + } +} + +void Client::runRequestConsumer() +{ + int attempts = 0; + const int max_attempts = 5; + bool connected = false; + ActiveMQConnectionFactory *connection_factory = nullptr; + + while (attempts < max_attempts && !connected) { + try + { + LOG4CXX_INFO(logger, "Initializing client attempt #" << attempts + 1 << " out of: " << max_attempts); + + // obtain new ConnectionFactory + connection_factory = new ActiveMQConnectionFactory(broker_uri); + connection = connection_factory->createConnection(artemis_user, artemis_password); + + connected = true; + LOG4CXX_INFO(logger, "Successfully connected to ActiveMQ broker"); + } + catch (CMSException e) + { + attempts++; + LOG4CXX_WARN(logger, "Connection attempt #" << attempts << " failed. Error: " << e.what()); + + // Clean up the factory if it was allocated but connection failed + if (connection_factory) { + delete connection_factory; + connection_factory = nullptr; + } + + if (attempts < max_attempts) { + LOG4CXX_INFO(logger, "Retrying in 5 seconds..."); + std::this_thread::sleep_for(std::chrono::seconds(5)); + } else { + LOG4CXX_ERROR(logger, "Reached maximum connection attempts. Exiting."); + LOG4CXX_ERROR(logger, "Make sure the ActiveMQ server is accessible at " << broker_uri); + exit(1); + } + } + } + + try + { + // start connection now that consumers created + connection->start(); + connection->setExceptionListener(this); + + // session = connection->createSession(Session::AUTO_ACKNOWLEDGE); + session = connection->createSession(Session::DUPS_OK_ACKNOWLEDGE); + request_dest = session->createQueue(requests_dest_name); + response_dest = session->createQueue(response_dest_name); + // Setup a producer to put messages on IWP response queue + producer = session->createProducer(response_dest); + producer->setDeliveryMode(DeliveryMode::NON_PERSISTENT); + + LOG4CXX_DEBUG(logger, "Using request queue: " << requests_dest_name); + LOG4CXX_DEBUG(logger, "Using response queue: " << response_dest_name); + + // Setup a consumer to consume messages off IWP requst queue + consumer = session->createConsumer(request_dest); + // Asynchronous MessageListener consumption + consumer->setMessageListener(this); + + delete connection_factory; // connection created, delete connection_factory + LOG4CXX_INFO(logger, "Finished client initialization"); + } + catch (CMSException e) + { + LOG4CXX_ERROR(logger, "Error occured during client initialization: " + "CMSException - " + << e.getCause()->what() << ". Exiting."); + LOG4CXX_ERROR(logger, "Make sure the ActiveMQ server is accessible at " << broker_uri); + e.printStackTrace(); + exit(1); + } +} + +void Client::onMessage(const Message *message) +{ + try + { + LOG4CXX_INFO(logger, "Message received"); + vector message_properties = message->getPropertyNames(); + + const BytesMessage *msg = dynamic_cast(message); + if (!msg) + { + LOG4CXX_ERROR(logger, "Incorrect message type received from " + "ActiveMQ, aborting"); + return; + } + LOG4CXX_INFO(logger, "Message body length: " << msg->getBodyLength()); + + // request + ImageServiceQuery acii_query; + + AciiServiceResponse acii_serviceresponse; + + if (acii_query.ParseFromArray(msg->getBodyBytes(), msg->getBodyLength())) + { + LOG4CXX_INFO(logger, "Processing message..."); + if (!acii_query.path().empty()) + { + LOG4CXX_DEBUG(logger, "Received image URL " << acii_query.path()); + } + LOG4CXX_INFO(logger, "ActiveMQ Message ID " << acii_query.imageid()); + + acii_serviceresponse.set_imageid(acii_query.imageid()); + + string image_path = Client::getImage(acii_query.imageid(), acii_query.path(), + acii_query.imagebytes()); + int image_type = acii_query.imagetype(); + int image_width = acii_query.width(); + int image_height = acii_query.height(); + bool imgAllowed = true; + + if (!image_path.empty()) + { + int up_down = -1; + int left_right = -1; + + manager->run(image_path, up_down, left_right, image_type, image_width, + image_height); + if (up_down == -1 || left_right == -1) + { + acii_serviceresponse.set_vertical(VerticalAlignment::UnknownVertical); + acii_serviceresponse.set_horizontal(HorizontalAlignment::UnknownHorizontal); + } + else + { + switch (up_down) + { + case 0: + acii_serviceresponse.set_vertical(VerticalAlignment::Up); + break; + case 1: + acii_serviceresponse.set_vertical(VerticalAlignment::Down); + break; + default: + acii_serviceresponse.set_vertical(VerticalAlignment::UnknownVertical); + } + + switch (left_right) + { + case 0: + acii_serviceresponse.set_horizontal(HorizontalAlignment::Left); + break; + case 1: + acii_serviceresponse.set_horizontal(HorizontalAlignment::Right); + break; + default: + acii_serviceresponse.set_horizontal( + HorizontalAlignment::UnknownHorizontal); + } + } + + string data; + } + else + { + LOG4CXX_ERROR(logger, "Image not accepted. Responding with: ERROR"); + acii_serviceresponse.set_vertical(VerticalAlignment::UnknownVertical); + acii_serviceresponse.set_horizontal(HorizontalAlignment::UnknownHorizontal); + } + + if (!image_path.empty()) + { + remove(image_path.c_str()); // remove temp file + } + } + else + { + LOG4CXX_ERROR(logger, "Error occured parsing acii_query"); + } + + LOG4CXX_INFO(logger, "Processing response..."); + + // ByteSize() const' is deprecated: Please use ByteSizeLong() + size_t reply_size = acii_serviceresponse.ByteSizeLong(); + + unsigned char reply_bytes[reply_size]; + acii_serviceresponse.SerializeToArray(reply_bytes, reply_size); + + unique_ptr response( + session->createBytesMessage(reply_bytes, reply_size)); + producer->send(response.get()); + + LOG4CXX_INFO(logger, "Response sent " + "for ID: " + << acii_query.imageid()); + + LOG4CXX_INFO(logger, "Session committed ...well not anymore"); + } + catch (CMSException &e) + { + LOG4CXX_ERROR(logger, "Error occured during processing: " << e.what()); + e.printStackTrace(); + } + catch (exception &e) + { + LOG4CXX_ERROR(logger, "Error occured during processing: " << e.what()); + } + catch (...) + { + LOG4CXX_ERROR(logger, "An unknown error occured during processing"); + } +} + +void Client::onException(const CMSException &ex) +{ + LOG4CXX_ERROR(logger, "Exception occured: " << ex.what() << ". Exiting."); + exit(1); +} + +void Client::transportInterrupted() +{ + LOG4CXX_WARN(logger, "Transport interupted"); +} + +void Client::transportResumed() +{ + LOG4CXX_WARN(logger, "Transport resumed"); +} + +bool Client::imgAllowed(string &img_ext) +{ + bool ret = find(allowed_img_extensions.begin(), + allowed_img_extensions.end(), img_ext) != allowed_img_extensions.end(); + if (!ret) + { + LOG4CXX_ERROR(logger, "Image type '" << img_ext << "' not allowed"); + } + return ret; +} + +string Client::getImage(string id, string url_path, + const string bytes) +{ + CURL *curl; + FILE *fp; + CURLcode res; + + string str_path = this->output_path + id; + + if (!url_path.empty()) + { + LOG4CXX_INFO(logger, "URL - " << url_path); + } + LOG4CXX_INFO(logger, "Output file name " << str_path); + + string local_path = ""; + + try + { + curl = curl_easy_init(); + if (curl) + { + fp = fopen(str_path.c_str(), "wb"); + if (fp) + { + if (url_path.length()) + { + curl_easy_setopt(curl, CURLOPT_URL, url_path.c_str()); + curl_easy_setopt(curl, CURLOPT_HEADER, false); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Client::write_data); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + res = curl_easy_perform(curl); + if (res != CURLE_OK) + { + LOG4CXX_ERROR(logger, "curl_easy_perform() failed: %s\n" + << curl_easy_strerror(res) << "\n"); + } + curl_easy_cleanup(curl); + } + else + { + fwrite(bytes.c_str(), sizeof(char), bytes.length(), fp); + } + fclose(fp); + } + else + { + LOG4CXX_ERROR(logger, "Unable to open output file " << str_path); + return ""; + } + local_path = str_path; + } + else + { + LOG4CXX_ERROR(logger, "Error initialize curl object"); + } + } + catch (exception &e) + { + LOG4CXX_ERROR(logger, "Error downloading image to local " << e.what()); + } + + return local_path; +} + +size_t Client::write_data(void *contents, size_t size, size_t nmemb, + FILE *stream) +{ + size_t written; + written = fwrite(contents, size, nmemb, stream); + + return written; +} diff --git a/acii/acii-client/src/EOC2Eye.cpp b/acii/acii-client/src/EOC2Eye.cpp new file mode 100755 index 0000000..7178293 --- /dev/null +++ b/acii/acii-client/src/EOC2Eye.cpp @@ -0,0 +1,768 @@ +/***************************************************************************** + * Eye Orientation CLassifier v2 (U/D + L/R) + * Author: Adam Czajka, aczajka@nd.edu, May 2016 + * + * Code in this file is partially based on Open Source for Iris (OSIRIS) v. 4.1 + * under BSD license http://opensource.org/licenses/bsd-license.php + ******************************************************************************/ + +#include "EOC2Eye.h" +#include "EOC2Processings.h" +#include +#include +#include +#include +#include +#include + + +using cv::BORDER_REFLECT; +using cv::INTER_CUBIC; +using cv::Mat; +using cv::Rect; +using cv::Scalar; +using cv::Scharr; +using cv::meanStdDev; +using cv::resize; +#ifdef EOC2_DEBUG +using std::cout; +#endif +using std::endl; +using std::exception; +using std::max; +using std::min; +using std::runtime_error; +using std::string; +using org::mitre::iwp::buffers::ImageServiceQuery; +#ifdef EOC2_DEBUG +using std::ofstream; +#endif + +const extern log4cxx::LoggerPtr logger; + +namespace EOC2 { + EOC2Eye::EOC2Eye(int image_type, int image_width, int image_height) { + imageType = image_type; + imageWidth = image_width; + imageHeight = image_height; + mpOriginalImage = 0; + mpMask = 0; + mpNormalizedImage = 0; + mpNormalizedMask = 0; + mPupil.setCircle(0, 0, 0); + mIris.setCircle(0, 0, 0); + iNumberOfUDfeatures = 6; + iNumberOfLRfeatures = 2; + + bProcessedOK = false; + + // The following variable is needed + // if one needs to save the debug data - segmented iris image + // (not used in the final version) +#ifdef EOC2_DEBUG + mpSegmentedImage = 0; +#endif + } + + // Default constructor + EOC2Eye::~EOC2Eye() { + cvReleaseImage(&mpOriginalImage); + cvReleaseImage(&mpMask); + cvReleaseImage(&mpNormalizedImage); + cvReleaseImage(&mpNormalizedMask); + + // The following deallocation is needed + // if one needs to save the debug data - segmented iris image + // (not used in the final version) +#ifdef EOC2_DEBUG + cvReleaseImage(&mpSegmentedImage); +#endif + } + + + ///////////////////////////////////////////////////////// + // Feature extraction + + void EOC2Eye::featuresUD() { + ///////////////////////////////////////////////////////// + // Vertical and horizontal shifts + + stFeaturesUD.fVerticalPupilShift = mPupil.getCenterFloat().y - + mIris.getCenterFloat().y; + + + + ///////////////////////////////////////////////////////// + // Eyelid coverage + + int w, h; + float upperEyelidNotCovered = 0.0, lowerEyelidNotCovered = 0.0; + int rWidthOfNormalizedIris = mpNormalizedMask->width; + int rHeightOfNormalizedIris = mpNormalizedMask->height; + + for (w = static_cast(round(rWidthOfNormalizedIris / 8)); + w < static_cast(round(3 * rWidthOfNormalizedIris / 8)); ++w) { + for (h = static_cast(round(rHeightOfNormalizedIris / 2)); + h < rHeightOfNormalizedIris; ++h) { + upperEyelidNotCovered += static_cast( + reinterpret_cast(mpNormalizedMask->imageData + + h*rWidthOfNormalizedIris)[w]); + } + } + + for (w = static_cast(round(5 * rWidthOfNormalizedIris / 8)); + w < static_cast(round(7 * rWidthOfNormalizedIris / 8)); ++w) { + for (h = static_cast(round(rHeightOfNormalizedIris / 2)); + h < rHeightOfNormalizedIris; ++h) { + lowerEyelidNotCovered += static_cast( + reinterpret_cast(mpNormalizedMask->imageData + + h*rWidthOfNormalizedIris)[w]); + } + } + + stFeaturesUD.fEyelidCoverage = + (lowerEyelidNotCovered - upperEyelidNotCovered) / + ((255 * static_cast(rWidthOfNormalizedIris) * + static_cast(rHeightOfNormalizedIris)) / 8); + + + + ///////////////////////////////////////////////////////// + // Eyelash value for upright / upside-down + + float upperEyelidValue, lowerEyelidValue, fElts; + int rWidthOfOriginalIris = mpOriginalImage->width; + int rHeightOfOriginalIris = mpOriginalImage->height; + int hStart, hStop, wStart, wStop; + float fWidthV = 1.4f; + float fHeightV = 0.2f; + + // upper eyelid + hStart = static_cast(max(0.0, mPupil.getCenterFloat().y - + mPupil.getRadiusFloat() - roundf(fHeightV*mIris.getRadiusFloat()))); + hStop = static_cast(max(0.0, mPupil.getCenterFloat().y - + mPupil.getRadiusFloat())); + wStart = static_cast(max(0.0, mPupil.getCenterFloat().x - + roundf(fWidthV*mIris.getRadiusFloat()))); + wStop = static_cast(min( + static_cast(rWidthOfOriginalIris), + mPupil.getCenterFloat().x + roundf(fWidthV*mIris.getRadiusFloat()))); + + fElts = 0.0; + upperEyelidValue = 0.0; + for (w = wStart; w < wStop; ++w) { + for (h = hStart; h < hStop; ++h) { + upperEyelidValue += static_cast( + reinterpret_cast(mpOriginalImage->imageData + + h*rWidthOfOriginalIris)[w]); + fElts += 1.0; + } + } + if (fElts > 0) { + upperEyelidValue /= fElts; + } else { + throw runtime_error("Not enough data to calculate EINV feature"); + } + + // lower eyelid + hStart = static_cast(min( + static_cast(rHeightOfOriginalIris), + mPupil.getCenterFloat().y + mPupil.getRadiusFloat())); + hStop = static_cast(min( + static_cast(rHeightOfOriginalIris), + mPupil.getCenterFloat().y + mPupil.getRadiusFloat() + + roundf(fHeightV*mIris.getRadiusFloat()))); + + fElts = 0.0; + lowerEyelidValue = 0.0; + for (w = wStart; w < wStop; ++w) + for (h = hStart; h < hStop; ++h) { + lowerEyelidValue += static_cast(( + reinterpret_cast(mpOriginalImage->imageData + + h*rWidthOfOriginalIris))[w]); + fElts += 1.0; + } + if (fElts > 0) + lowerEyelidValue /= fElts; + else + throw runtime_error("Not enough data to calculate EINV feature"); + + stFeaturesUD.fEyelidValue = upperEyelidValue - lowerEyelidValue; + + + + ///////////////////////////////////////////////////////// + // Features based on fitered eyelash regions + + // global parameters and variables + fWidthV = 1.1f; + float fScale = 0.45f; + Mat upperEyelashScaled, upperEyelashScaledDx, upperEyelashScaledDy, + lowerEyelashScaled, lowerEyelashScaledDx, lowerEyelashScaledDy; + + // process upper eyelash region + + // cut appropriate portions of the eyelash areas + hStart = static_cast(max(0.0, + mPupil.getCenterFloat().y - 2 * mIris.getRadiusFloat())); + hStop = static_cast(max(0.0, + mPupil.getCenterFloat().y - mPupil.getRadiusFloat())); + wStart = static_cast(max(0.0, + mPupil.getCenterFloat().x - roundf(fWidthV*mIris.getRadiusFloat()))); + wStop = static_cast(min( + static_cast(rWidthOfOriginalIris), + mPupil.getCenterFloat().x + roundf(fWidthV*mIris.getRadiusFloat()))); + + if ((wStop - wStart <= 0) || (hStop - hStart) <= 0) { + throw runtime_error( + "Not enough data to calculate Scharr-filter-based features"); + } + + Mat upperEyelash(mpOriginalImage, Rect(wStart, hStart, + wStop - wStart, hStop - hStart)); + + // scale selected regions + resize(upperEyelash, upperEyelashScaled, cvSize(0, 0), fScale, fScale, + INTER_CUBIC); + + // estimate the vertical and horizontal gradients using Scharr filters + Scharr(upperEyelashScaled, upperEyelashScaledDx, CV_32F, 1, 0, 1.0, 0.0, + BORDER_REFLECT); + Scharr(upperEyelashScaled, upperEyelashScaledDy, CV_32F, 0, 1, 1.0, 0.0, + BORDER_REFLECT); + + // Since Scharr calculates correlation (NOT convolution) we need to process + // negative results (conv = -corr) + upperEyelashScaledDx = -upperEyelashScaledDx; + upperEyelashScaledDy = -upperEyelashScaledDy; + + + // process lower eyelash region + + // cut appropriate portions of the eyelash areas + hStart = static_cast(min( + static_cast(rHeightOfOriginalIris), + mPupil.getCenterFloat().y + mPupil.getRadiusFloat())); + hStop = static_cast(min( + static_cast(rHeightOfOriginalIris), + mPupil.getCenterFloat().y + 2 * mIris.getRadiusFloat())); + + if ((wStop - wStart <= 0) || (hStop - hStart) <= 0) { + throw runtime_error( + "Not enough data to calculate Scharr-filter-based features"); + } + + Mat lowerEyelash(mpOriginalImage, Rect(wStart, hStart, + wStop - wStart, hStop - hStart)); + + // scale selected regions + resize(lowerEyelash, lowerEyelashScaled, cvSize(0, 0), fScale, fScale, + INTER_CUBIC); + + // estimate the vertical and horizontal gradients using Scharr filters + // void Scharr(InputArray src, OutputArray dst, int ddepth, int dx, int dy, + // double scale=1, double delta=0, int borderType=BORDER_DEFAULT) + Scharr(lowerEyelashScaled, lowerEyelashScaledDx, CV_32F, 1, 0, 1.0, 0.0, + BORDER_REFLECT); + Scharr(lowerEyelashScaled, lowerEyelashScaledDy, CV_32F, 0, 1, 1.0, 0.0, + BORDER_REFLECT); + + // Since Scharr calculates correlation (NOT convolution) we need to process + // negative results (conv = -corr) + lowerEyelashScaledDx = -lowerEyelashScaledDx; + lowerEyelashScaledDy = -lowerEyelashScaledDy; + + + // calculate the final gradient magnitude and angles + + // upper eyelid + CvSize sizeU = upperEyelashScaledDx.size(); + Mat fuMagMat(sizeU, CV_32F); + Mat fuCosMat(sizeU, CV_32F), fuSinMat(sizeU, CV_32F); + Scalar fuMagMean, fuMagStd, fuCosMean, fuCosStd, fuSinMean, fuSinStd; + float fAngleTemp; + + for (w = 0; w < sizeU.width; ++w) { + for (h = 0; h < sizeU.height; ++h) { + fuMagMat.at(h, w) = sqrt(upperEyelashScaledDx.at(h, w) * + upperEyelashScaledDx.at(h, w) + + upperEyelashScaledDy.at(h, w) * + upperEyelashScaledDy.at(h, w)); + fAngleTemp = atan2(upperEyelashScaledDy.at(h, w), + upperEyelashScaledDx.at(h, w)); + + if (fAngleTemp < 0) { + fuCosMat.at(h, w) = cos(fAngleTemp + 2 * OSI_PI); + fuSinMat.at(h, w) = sin(fAngleTemp + 2 * OSI_PI); + } else { + fuCosMat.at(h, w) = cos(fAngleTemp); + fuSinMat.at(h, w) = sin(fAngleTemp); + } + } + } + meanStdDev(fuMagMat, fuMagMean, fuMagStd); + meanStdDev(fuCosMat, fuCosMean, fuCosStd); + meanStdDev(fuSinMat, fuSinMean, fuSinStd); + + // lower eyelid + CvSize sizeL = lowerEyelashScaledDx.size(); + CvSize sizeL2 = lowerEyelashScaledDy.size(); + + Mat flMagMat(sizeL, CV_32F); + Mat flCosMat(sizeL, CV_32F), flSinMat(sizeL, CV_32F); + Scalar flMagMean, flMagStd, flCosMean, flCosStd, flSinMean, flSinStd; + + for (w = 0; w < sizeL.width; ++w) { + for (h = 0; h < sizeL.height; ++h) { + flMagMat.at(h, w) = sqrt( + lowerEyelashScaledDx.at(h, w) * + lowerEyelashScaledDx.at(h, w) + + lowerEyelashScaledDy.at(h, w) * + lowerEyelashScaledDy.at(h, w)); + fAngleTemp = atan2(lowerEyelashScaledDy.at(h, w), + lowerEyelashScaledDx.at(h, w)); + if (fAngleTemp < 0) { + flCosMat.at(h, w) = cos(fAngleTemp + 2 * OSI_PI); + flSinMat.at(h, w) = sin(fAngleTemp + 2 * OSI_PI); + } else { + flCosMat.at(h, w) = cos(fAngleTemp); + flSinMat.at(h, w) = sin(fAngleTemp); + } + } + } + meanStdDev(flMagMat, flMagMean, flMagStd); + meanStdDev(flCosMat, flCosMean, flCosStd); + meanStdDev(flSinMat, flSinMean, flSinStd); + + stFeaturesUD.fEyelidFiltered = + static_cast(fuMagMean.val[0] - flMagMean.val[0]); + stFeaturesUD.fEyelidFilteredCosVar = + static_cast(((fuCosStd.val[0]) * (fuCosStd.val[0]) - + (flCosStd.val[0]) * (flCosStd.val[0]))); + stFeaturesUD.fEyelidFilteredSinVar = + static_cast(((fuSinStd.val[0]) * (fuSinStd.val[0]) - + (flSinStd.val[0]) * (flSinStd.val[0]))); + + // Release memory + upperEyelash.release(); + upperEyelashScaled.release(); + upperEyelashScaledDx.release(); + upperEyelashScaledDy.release(); + lowerEyelash.release(); + lowerEyelashScaled.release(); + lowerEyelashScaledDx.release(); + lowerEyelashScaledDy.release();; + fuMagMat.release(); + fuCosMat.release(); + fuSinMat.release(); + flMagMat.release(); + flCosMat.release(); + flSinMat.release(); + } + + + + void EOC2Eye::featuresLR() { + ///////////////////////////////////////////////////////// + // Vertical and horizontal shifts + + stFeaturesLR.fHorizontalPupilShift = + mPupil.getCenterFloat().x - mIris.getCenterFloat().x; + + + ///////////////////////////////////////////////////////// + // Eyelash value for recognition of left / right + + int hStart, hStop, wStart, wStop, w, h; + int rWidthOfOriginalIris = mpOriginalImage->width; + int rHeightOfOriginalIris = mpOriginalImage->height; + float leftUpperEyelidValue, leftLowerEyelidValue, rightUpperEyelidValue; + float rightLowerEyelidValue, fElts; + + // left upper eyelash region + hStart = static_cast(max(0.0, + mPupil.getCenterFloat().y - mIris.getRadiusFloat() / 2)); + hStop = static_cast(max(0.0, mPupil.getCenterFloat().y)); + wStart = static_cast(max(0.0, + mPupil.getCenterFloat().x - mPupil.getRadiusFloat() - 2 * + mIris.getRadiusFloat())); + wStop = static_cast(max(0.0, + mPupil.getCenterFloat().x - mPupil.getRadiusFloat() - + mIris.getRadiusFloat())); + + fElts = 0.0; + leftUpperEyelidValue = 0.0; + for (w = wStart; w < wStop; ++w) { + for (h = hStart; h < hStop; ++h) { + leftUpperEyelidValue += static_cast( + reinterpret_cast(mpOriginalImage->imageData + + h*rWidthOfOriginalIris)[w]); + fElts += 1.0; + } + } + if (fElts > 0) { + leftUpperEyelidValue /= fElts; + } else { + throw runtime_error("Not enough data to calculate EINH feature"); + } + + // right upper eyelash region + wStart = static_cast(min( + static_cast(rWidthOfOriginalIris), + mPupil.getCenterFloat().x + mPupil.getRadiusFloat() + + mIris.getRadiusFloat())); + wStop = static_cast(min( + static_cast(rWidthOfOriginalIris), + mPupil.getCenterFloat().x + mPupil.getRadiusFloat() + + 2 * mIris.getRadiusFloat())); + + fElts = 0.0; + rightUpperEyelidValue = 0.0; + for (w = wStart; w < wStop; ++w) { + for (h = hStart; h < hStop; ++h) { + rightUpperEyelidValue += static_cast( + reinterpret_cast(mpOriginalImage->imageData + + h*rWidthOfOriginalIris)[w]); + fElts += 1.0; + } + } + if (fElts > 0) { + rightUpperEyelidValue /= fElts; + } else { + throw runtime_error("Not enough data to calculate EINH feature"); + } + + + // left lower eyelash region + hStart = static_cast(max(0.0, mPupil.getCenterFloat().y)); + hStop = static_cast(max(0.0, + mPupil.getCenterFloat().y + mIris.getRadiusFloat() / 2)); + wStart = static_cast(max(0.0, + mPupil.getCenterFloat().x - mPupil.getRadiusFloat() - + 2 * mIris.getRadiusFloat())); + wStop = static_cast(max(0.0, + mPupil.getCenterFloat().x - mPupil.getRadiusFloat() - + mIris.getRadiusFloat())); + + fElts = 0.0; + leftLowerEyelidValue = 0.0; + for (w = wStart; w < wStop; ++w) { + for (h = hStart; h < hStop; ++h) { + leftLowerEyelidValue += static_cast( + reinterpret_cast(mpOriginalImage->imageData + + h*rWidthOfOriginalIris)[w]); + fElts += 1.0; + } + } + if (fElts > 0) { + leftLowerEyelidValue /= fElts; + } else { + throw runtime_error("Not enough data to calculate EINH feature"); + } + + + // right lower eyelash region + wStart = static_cast(max(0.0, + mPupil.getCenterFloat().x + mPupil.getRadiusFloat() + + mIris.getRadiusFloat())); + wStop = static_cast(max(0.0, + mPupil.getCenterFloat().x + mPupil.getRadiusFloat() + + 2 * mIris.getRadiusFloat())); + + fElts = 0.0; + rightLowerEyelidValue = 0.0; + for (w = wStart; w < wStop; ++w) { + for (h = hStart; h < hStop; ++h) { + rightLowerEyelidValue += static_cast( + reinterpret_cast(mpOriginalImage->imageData + + h*rWidthOfOriginalIris)[w]); + fElts += 1.0; + } + } + if (fElts) { + rightLowerEyelidValue /= fElts; + } else { + throw runtime_error("Not enough data to calculate EINH feature"); + } + + stFeaturesLR.fEyelidValue = min(leftUpperEyelidValue, + leftLowerEyelidValue) - min(rightUpperEyelidValue, + rightLowerEyelidValue); + } + + +#ifdef EOC2_DEBUG + void EOC2Eye::printFeaturesUD(int &iFileNo, const string &rFilename, + const string &label) { + cout.precision(6); + cout.setf(std::ios::fixed, std::ios::floatfield); + cout << iFileNo << "\t" << rFilename << "\t\t" + << label << "\t\t" + << stFeaturesUD.fEyelidCoverage << "\t" + << stFeaturesUD.fVerticalPupilShift << "\t" + << stFeaturesUD.fEyelidValue << "\t" + << stFeaturesUD.fEyelidFiltered << "\t" + << stFeaturesUD.fEyelidFilteredSinVar << "\t" + << stFeaturesUD.fEyelidFilteredCosVar << endl; + } + + void EOC2Eye::printFeaturesUDHeader() { + cout << "FileNo\tFilename\t\tLabel\t\tEOCC\t\tPIVO\t\tEINV\t\tGMAG\t\t" + "SIND\t\tCOSD" << endl; + } + + + void EOC2Eye::printFeaturesLR(int &iFileNo, const string &rFilename, + const string &label) { + cout.precision(6); + cout.setf(std::ios::fixed, std::ios::floatfield); + cout << iFileNo << "\t" << rFilename << "\t\t" + << label << "\t\t" + << stFeaturesLR.fEyelidValue << "\t" + << stFeaturesLR.fHorizontalPupilShift << endl; + } + + void EOC2Eye::printFeaturesLRHeader() { + cout << "FileNo\tFilename\t\tLabel\t\tPIHO\t\tEINH" << endl; + } +#endif + + int EOC2Eye::getNumberOfLRfeatures() { return iNumberOfLRfeatures; } + + int EOC2Eye::getNumberOfUDfeatures() { return iNumberOfUDfeatures; } + + + void EOC2Eye::loadImage(const string &rFilename, IplImage **ppImage) { + try { + if (*ppImage) { + cvReleaseImage(ppImage); + } + + *ppImage = cvLoadImage(rFilename.c_str(), 0); + if (!*ppImage) { + LOG4CXX_ERROR(logger, "Cannot load image : " << rFilename); + } + } catch (exception &e) { + LOG4CXX_ERROR(logger, e.what()); + } + } + + void EOC2Eye::loadPixmap(const string &rFilename) { + + int channels; + Mat imageMat; + FILE *fp = fopen(rFilename.c_str(), "rb"); + if (!fp) { + LOG4CXX_ERROR(logger, "Cannot load image : " << rFilename); + return; + } + // Determine the number of channels + fseek(fp, 0, SEEK_END); + channels = ftell(fp) / imageHeight / imageWidth; + fseek(fp, 0, SEEK_SET); + if (channels == 3) { + imageMat = Mat::zeros(imageHeight, imageWidth, CV_8UC3); + fread(imageMat.data, 3, imageWidth * imageHeight, fp); + IplImage *colorImage = new IplImage(imageMat); + this->mpOriginalImage = cvCreateImage(cvGetSize(colorImage), IPL_DEPTH_8U, + 1); + cvCvtColor(colorImage, this->mpOriginalImage, CV_RGB2GRAY); + delete colorImage; + } else { + imageMat = Mat::zeros(imageHeight, imageWidth, CV_8UC1); + fread(imageMat.data, 1, imageWidth * imageHeight, fp); + // we need to create an image and clone it to prevent the underlying + // matrix from being freed too early. + IplImage tmpImage(imageMat); + this->mpOriginalImage = cvCloneImage(&tmpImage); + } + fclose(fp); + } + + void EOC2Eye::loadOriginalImage(const string &rFilename) { + if (imageType == ImageServiceQuery::PIXMAP) { + loadPixmap(rFilename); + } else { + loadImage(rFilename, &mpOriginalImage); + } + } + + + + void EOC2Eye::initMask() { + if (mpMask) { + cvReleaseImage(&mpMask); + } + if (!mpOriginalImage) { + throw runtime_error( + "Cannot initialize the mask because original image is not loaded"); + } + mpMask = cvCreateImage(cvGetSize(mpOriginalImage), IPL_DEPTH_8U, 1); + cvSet(mpMask, cvScalar(255)); + } + + + + void EOC2Eye::segment(int minIrisDiameter, int minPupilDiameter, + int maxIrisDiameter, int maxPupilDiameter) { + if (!mpOriginalImage) { + throw runtime_error( + "Cannot segment image because original image is not loaded"); + } + + // Initialize mask and segmented image + mpMask = cvCreateImage(cvGetSize(mpOriginalImage), IPL_DEPTH_8U, 1); + + // Processing functions + EOC2Processings op; + + // Segment the eye + op.segment(mpOriginalImage, + mpMask, + mPupil, + mIris, + mThetaCoarsePupil, + mThetaCoarseIris, + mCoarsePupilContour, + mCoarseIrisContour, + minIrisDiameter, + minPupilDiameter, + maxIrisDiameter, + maxPupilDiameter); + +#ifdef EOC2_DEBUG + // The following code is needed if one needs to save the debug data + // and draw the iris segmentation results + mpSegmentedImage = cvCreateImage(cvGetSize(mpOriginalImage), + IPL_DEPTH_8U, 3); + cvCvtColor(mpOriginalImage, mpSegmentedImage, CV_GRAY2BGR); + IplImage * tmp = cvCloneImage(mpMask); + cvZero(tmp); + cvCircle(tmp, mIris.getCenter(), mIris.getRadius(), cvScalar(255), -1); + cvCircle(tmp, mPupil.getCenter(), mPupil.getRadius(), cvScalar(0), -1); + cvSub(tmp, mpMask, tmp); + cvSet(mpSegmentedImage, cvScalar(0, 0, 255), tmp); + cvReleaseImage(&tmp); + cvCircle(mpSegmentedImage, mPupil.getCenter(), mPupil.getRadius(), + cvScalar(0, 255, 0)); + cvCircle(mpSegmentedImage, mIris.getCenter(), mIris.getRadius(), + cvScalar(0, 255, 0)); +#endif + } + + + void EOC2Eye::normalize(int rWidthOfNormalizedIris, + int rHeightOfNormalizedIris) { + // Processing functions + EOC2Processings op; + + if (!mpOriginalImage) { + throw runtime_error( + "Cannot normalize image because original image is not loaded"); + } + + mpNormalizedImage = cvCreateImage( + cvSize(rWidthOfNormalizedIris, rHeightOfNormalizedIris), + IPL_DEPTH_8U, 1); + + if (mThetaCoarsePupil.empty() || mThetaCoarseIris.empty()) + throw runtime_error("Cannot normalize image because contours are not " + "correctly computed/loaded"); + + op.normalizeFromContour(mpOriginalImage, + mpNormalizedImage, + mPupil, + mIris, + mThetaCoarsePupil, + mThetaCoarseIris, + mCoarsePupilContour, + mCoarseIrisContour); + + if (!mpMask) { + initMask(); + } + + mpNormalizedMask = cvCreateImage(cvSize(rWidthOfNormalizedIris, + rHeightOfNormalizedIris), IPL_DEPTH_8U, 1); + op.normalizeFromContour(mpMask, + mpNormalizedMask, + mPupil, + mIris, + mThetaCoarsePupil, + mThetaCoarseIris, + mCoarsePupilContour, + mCoarseIrisContour); + } + + + + + /////////////////////////////////////////////////////////////////// + // Functions for saving debug data (not used in the final version) + /////////////////////////////////////////////////////////////////// +#ifdef EOC2_DEBUG + + void EOC2Eye::saveImage(const string &rFilename, const IplImage * pImage) { + // :TODO: no exception here, but 2 error messages + // 1. pImage does NOT exist => "image was neither comptued nor loaded" + // 2. cvSaveImage returns <=0 => "rFilename = invalid for saving" + if (!pImage) { + throw runtime_error("Cannot save image " + rFilename + + " because this image is not built"); + } + if (!cvSaveImage(rFilename.c_str(), pImage)) { + LOG4CXX_ERROR(logger, "Cannot save image " << rFilename); + } + } + + void EOC2Eye::saveSegmentedImage(const string &rFilename) { + saveImage(rFilename, mpSegmentedImage); + } + + void EOC2Eye::saveMask(const string &Filename) { + saveImage(rFilename, mpMask); + } + + void EOC2Eye::saveNormalizedImage(const string &rFilename) { + saveImage(rFilename, mpNormalizedImage); + } + + void EOC2Eye::saveNormalizedMask(const string &rFilename) { + saveImage(rFilename, mpNormalizedMask); + } + + void EOC2Eye::saveParameters(const string &rFilename) { + // Open the file + ofstream file(rFilename.c_str(), ofstream::out); + + // If file is not opened + if (!file) { + throw runtime_error("Cannot save the parameters in " + rFilename); + } + + try { + file << mCoarsePupilContour.size() << endl; + file << mCoarseIrisContour.size() << endl; + for (int i = 0; i < static_cast(mCoarsePupilContour.size()); i++) { + file << mCoarsePupilContour[i].x << " "; + file << mCoarsePupilContour[i].y << " "; + file << mThetaCoarsePupil[i] << " "; + } + file << endl; + for (int j = 0; j < static_cast(mCoarseIrisContour.size()); j++) { + file << mCoarseIrisContour[j].x << " "; + file << mCoarseIrisContour[j].y << " "; + file << mThetaCoarseIris[j] << " "; + } + } catch (exception &e) { + LOG4CXX_ERROR(logger, e.what()); + throw runtime_error("Error while saving parameters in " + rFilename); + } + + // Close the file + file.close(); + } +#endif + + +} // namespace EOC2 diff --git a/acii/acii-client/src/EOC2Manager.cpp b/acii/acii-client/src/EOC2Manager.cpp new file mode 100755 index 0000000..cb68c14 --- /dev/null +++ b/acii/acii-client/src/EOC2Manager.cpp @@ -0,0 +1,617 @@ +/***************************************************************************** + * Eye Orientation CLassifier v2 (U/D + L/R) + * Author: Adam Czajka, aczajka@nd.edu, May 2016 + * + * Code in this file is partially based on Open Source for Iris (OSIRIS) v. 4.1 + * under BSD license http://opensource.org/licenses/bsd-license.php + ******************************************************************************/ + +#include "EOC2Manager.h" +#include "EOC2Utils.h" +#include +#include +#include +#include + +using cv::FileStorage; +using cv::Mat; +using std::exception; +using std::ifstream; +using std::runtime_error; +using std::string; + +namespace EOC2 { + // Default constructor + EOC2Manager::EOC2Manager(bool keep_class_in_mem) { + logger = log4cxx::Logger::getLogger("acii.eyeclassifier"); + + this->keep_class_in_mem = keep_class_in_mem; + + SVM_UD = NULL; + colSTD_UD = NULL; + colMEAN_UD = NULL; + SVM_LR = NULL; + colSTD_LR = NULL; + colMEAN_LR = NULL; + + // Associate lines of configuration file to the attributes + + mMapString["XML filename for upright / upside-down classifier"] = + &mClassifierFileNameUD; + mMapString["XML filename for left / right classifier"] = + &mClassifierFileNameLR; + mMapString["XML filename for upright / upside-down feature normalization " + "coefficients"] = &mFeatureNormFileNameUD; + mMapString["XML filename for left / right feature normalization " + "coefficients"] = &mFeatureNormFileNameLR; + + mMapFloat["RBF gamma used in training"] = &mRBFgamma; + + // Iris segmentation section + mMapInt["Minimum diameter for pupil"] = &mMinPupilDiameter; + mMapInt["Maximum diameter for pupil"] = &mMaxPupilDiameter; + mMapInt["Minimum diameter for iris"] = &mMinIrisDiameter; + mMapInt["Maximum diameter for iris"] = &mMaxIrisDiameter; + + // Initialize all parameters + initConfiguration(); + +#ifdef EOC2_DEBUG + // Operations needed if debug data is saved (not used in the final version): + mMapString["Suffix for segmented images"] = &mSuffixSegmentedImages; + mMapString["Suffix for parameters"] = &mSuffixParameters; + mMapString["Suffix for masks of iris"] = &mSuffixMasks; + mMapString["Suffix for normalized images"] = &mSuffixNormalizedImages; + mMapString["Suffix for normalized masks"] = &mSuffixNormalizedMasks; + + mMapString["Save segmented images"] = &mOutputDirSegmentedImages; + mMapString["Save contours parameters"] = &mOutputDirParameters; + mMapString["Save masks of iris"] = &mOutputDirMasks; + mMapString["Save normalized images"] = &mOutputDirNormalizedImages; + mMapString["Save normalized masks"] = &mOutputDirNormalizedMasks; +#endif + } + + EOC2Manager::~EOC2Manager() { + cleanupClassifier(); + } + + // Initialize all configuration parameters + void EOC2Manager::initConfiguration() { + mClassifierFileNameUD = ""; + mClassifierFileNameLR = ""; + mFeatureNormFileNameUD = ""; + mFeatureNormFileNameLR = ""; + + // Parameters + mMinPupilDiameter = 21; + mMaxPupilDiameter = 91; + mMinIrisDiameter = 99; + mMaxIrisDiameter = 399; + mWidthOfNormalizedIris = 512; + mHeightOfNormalizedIris = 64; + mRBFgamma = 0.5; + +#ifdef EOC2_DEBUG + // Initialization needed if debug data is saved + // (not used in the final version): + // Outputs + mOutputDirSegmentedImages = ""; + mOutputDirParameters = ""; + mOutputDirMasks = ""; + mOutputDirNormalizedImages = ""; + mOutputDirNormalizedMasks = ""; + + // Suffix for filenames + mSuffixSegmentedImages = "_segm.bmp"; + mSuffixParameters = "_para.txt"; + mSuffixMasks = "_mask.bmp"; + mSuffixNormalizedImages = "_imno.bmp"; + mSuffixNormalizedMasks = "_mano.bmp"; +#endif + } + + void EOC2Manager::loadClassifier_UD() { + EOC2Eye eyeGlobal; + FileStorage fs; + SVM_UD = new CvSVM(); + colSTD_UD = new Mat(1, eyeGlobal.getNumberOfUDfeatures(), CV_64FC1); + colMEAN_UD = new Mat(1, eyeGlobal.getNumberOfUDfeatures(), CV_64FC1); + + /////////////////////////////////////////////////////// + // Loading the trained SVM classifier + LOG4CXX_DEBUG(logger, "Loading trained UP/DOWN SVM classifier from " << + mClassifierFileNameUD); + + // Check if file exists + fs.open(mClassifierFileNameUD, FileStorage::READ); + if (!fs.isOpened()) { + LOG4CXX_ERROR(logger, "UP/DOWN SVM classifier file not found"); + throw runtime_error("UP/DOWN SVM classifier file not found"); + } + fs.release(); + + // We have the trained SVM; let's load it + SVM_UD->load(mClassifierFileNameUD.c_str()); + LOG4CXX_DEBUG(logger, "Finished loading UP/DOWN SVM classifier"); + + /////////////////////////////////////////////////////// + // Loading feature normalization coefficients + LOG4CXX_DEBUG(logger, "Loading UP/DOWN feature normalization coefficients " + "from " << mFeatureNormFileNameUD); + + // Check if file exists + fs.open(mFeatureNormFileNameUD, FileStorage::READ); + if (!fs.isOpened()) { + LOG4CXX_ERROR(logger, + "UP/DOWN feature normalization coefficients file not found"); + throw runtime_error( + "UP/DOWN feature normalization coefficients file not found"); + } + fs["MEAN"] >> *colMEAN_UD; + fs["STD"] >> *colSTD_UD; + fs.release(); + LOG4CXX_DEBUG(logger, + "Finished loading UP/DOWN feature normalization coefficients"); + + // Log feature normalization coefficients (if needed) + for (int i = 0; i < eyeGlobal.getNumberOfUDfeatures(); ++i) { + LOG4CXX_TRACE(logger, "UP/DOWN mean vector value " << + i << ": " << colMEAN_UD->at(i)); + } + for (int i = 0; i < eyeGlobal.getNumberOfUDfeatures(); ++i) { + LOG4CXX_TRACE(logger, "UP/DOWN std vector value " << i + << ": " << colSTD_UD->at(i)); + } + } + + // Loads classification data structures from disk + void EOC2Manager::loadClassifier_LR() { + EOC2Eye eyeGlobal; + FileStorage fs; + + SVM_LR = new CvSVM(); + colSTD_LR = new Mat(1, eyeGlobal.getNumberOfUDfeatures(), CV_64FC1); + colMEAN_LR = new Mat(1, eyeGlobal.getNumberOfUDfeatures(), CV_64FC1); + + /////////////////////////////////////////////////////// + // Loading the trained SVM classifier + LOG4CXX_DEBUG(logger, "Loading trained LEFT/RIGHT SVM classifier from " << + mClassifierFileNameLR); + + // Check if file exists + fs.open(mClassifierFileNameLR, FileStorage::READ); + if (!fs.isOpened()) { + LOG4CXX_ERROR(logger, "LEFT/RIGHT SVM classifier file not found"); + throw runtime_error("LEFT/RIGHT SVM classifier file not found"); + } + fs.release(); + + // We have the trained SVM; let's load it + SVM_LR->load(mClassifierFileNameLR.c_str()); + LOG4CXX_DEBUG(logger, "Finished loading LEFT/RIGHT SVM classifier"); + + /////////////////////////////////////////////////////// + // Loading feature normalization coefficients + LOG4CXX_DEBUG(logger, "Loading LEFT/RIGHT feature normalization " + "coefficients from " << mFeatureNormFileNameLR); + + // Check if file exists + fs.open(mFeatureNormFileNameLR, FileStorage::READ); + if (!fs.isOpened()) { + LOG4CXX_ERROR(logger, + "LEFT/RIGHT feature normalization coefficients file not found"); + throw runtime_error( + "LEFT/RIGHT feature normalization coefficients file not found"); + } + fs["MEAN"] >> *colMEAN_LR; + fs["STD"] >> *colSTD_LR; + fs.release(); + LOG4CXX_DEBUG(logger, + "Finished loading LEFT/RIGHT feature normalization coefficients"); + + + // Log feature normalization coefficients (if needed) + for (int i = 0; i < eyeGlobal.getNumberOfUDfeatures(); ++i) { + LOG4CXX_TRACE(logger, "LEFT/RIGHT mean vector value " << + i << ": " << colMEAN_LR->at(i)); + } + for (int i = 0; i < eyeGlobal.getNumberOfUDfeatures(); ++i) { + LOG4CXX_TRACE(logger, "LEFT/RIGHT std vector value " << + i << ": " << colSTD_LR->at(i)); + } + } + + void EOC2Manager::loadClassifier() { + EOC2Manager::loadClassifier_UD(); + EOC2Manager::loadClassifier_LR(); + } + + void EOC2Manager::cleanupClassifier_UD() { + delete SVM_UD; + delete colSTD_UD; + delete colMEAN_UD; + LOG4CXX_DEBUG(logger, "Removing UP / DOWN classifier from memory"); + } + + void EOC2Manager::cleanupClassifier_LR() { + delete SVM_LR; + delete colSTD_LR; + delete colMEAN_LR; + LOG4CXX_DEBUG(logger, "Removing LEFT / RIGHT classifier from memory"); + } + + void EOC2Manager::cleanupClassifier() { + cleanupClassifier_UD(); + cleanupClassifier_LR(); + } + + + /* + * Loads the configuration from a textfile (ini) + * + * @param rFilename The configuration file path + */ + void EOC2Manager::loadConfiguration(const string &rFilename) { + // Open the file + ifstream file(rFilename.c_str(), ifstream::in); + + if (!file.good()) { + LOG4CXX_ERROR(logger, "Cannot read configuration file "); + throw runtime_error("Cannot read configuration file " + rFilename); + } + + // Some string functions + EOC2StringUtils osu; + + // Loop on lines + while (file.good() && !file.eof()) { + // Get the new line + string line; + getline(file, line); + + // Filter out comments + if (!line.empty()) { + int pos = line.find('#'); + if (pos != string::npos) { + line = line.substr(0, pos); + } + } + + // Split line into key and value + if (!line.empty()) { + int pos = line.find("="); + + if (pos != string::npos) { + // Trim key and value + string key = osu.trim(line.substr(0, pos)); + string value = osu.trim(line.substr(pos + 1)); + + if (!key.empty() && !value.empty()) { + // Option is type bool + if (mMapBool.find(key) != mMapBool.end()) { + *mMapBool[key] = osu.fromString(value); + + // Option is type int + } else if (mMapInt.find(key) != mMapInt.end()) { + *mMapInt[key] = osu.fromString(value); + + // Option is type float + } else if (mMapFloat.find(key) != mMapFloat.end()) { + *mMapFloat[key] = osu.fromString(value); + + // Option is type string + } else if (mMapString.find(key) != mMapString.end()) { + *mMapString[key] = osu.convertSlashes(value); + + // Option is not stored in any mMap + } else { + LOG4CXX_ERROR(logger, + "Unknown option in configuration file : " << line); + } + } + } + } + } + if (keep_class_in_mem) { + EOC2Manager::loadClassifier(); + } + } + + // Show the configuration in prompt command + void EOC2Manager::showConfiguration() { + LOG4CXX_INFO(logger, + "Trained left / right classifier read from: " << mClassifierFileNameLR); + LOG4CXX_INFO(logger, "Feature normalization coefficients for left / right " + "classification read from: " << mFeatureNormFileNameLR); + + LOG4CXX_INFO(logger, "Trained upright / upside-down classifier read from: " + << mClassifierFileNameUD); + LOG4CXX_INFO(logger, "Feature normalization coefficients for upright / " + "upside-down classification read from: " << mFeatureNormFileNameUD); + + LOG4CXX_INFO(logger, "Pupil diameter range: (" << mMinPupilDiameter << ", " + << mMaxPupilDiameter << ")"); + LOG4CXX_INFO(logger, "Iris diameter range: (" << mMinIrisDiameter << ", " + << mMaxIrisDiameter << ")"); + } // end of function + + // Calculate the image features required for upright / upside-down + // classification. + void EOC2Manager::processUD(const string &rFileName, EOC2Eye &rEye) { + // Strings handle + EOC2StringUtils osu; + + // Load the image + rEye.loadOriginalImage(rFileName); + + + // Segment the iris + rEye.segment(mMinIrisDiameter, mMinPupilDiameter, mMaxIrisDiameter, + mMaxPupilDiameter); + + // Normalize the iris image (Cartesian -> polar) + rEye.normalize(mWidthOfNormalizedIris, mHeightOfNormalizedIris); + + // Extract features required for upright / upside-down classification + rEye.featuresUD(); + + // Since we are here (no exceptions thrown) the processing is succesful + rEye.bProcessedOK = true; + +#ifdef EOC2_DEBUG + // Save debug data - segmentation image, parameters, mask, normalized image + // and mask (not used in the final version) + + string short_name = rFileName.substr(0, rFileName.rfind(".")); + + if (mOutputDirSegmentedImages != "") { + rEye.saveSegmentedImage(mOutputDirSegmentedImages + short_name + + mSuffixSegmentedImages); + } + if ( mOutputDirParameters != "" ) { + rEye.saveParameters(mOutputDirParameters + short_name + + mSuffixParameters); + } + if ( mOutputDirMasks != "" ) { + rEye.saveMask(mOutputDirMasks + short_name + mSuffixMasks); + } + if ( mOutputDirNormalizedImages != "" ) { + rEye.saveNormalizedImage(mOutputDirNormalizedImages + short_name + + mSuffixNormalizedImages); + } + if ( mOutputDirNormalizedMasks != "" ) { + rEye.saveNormalizedMask(mOutputDirNormalizedMasks + short_name + + mSuffixNormalizedMasks); + } +#endif + } // end of function + + /** + * Calculates the image features required for left / right classification. + * + * @param rFileName The local path to the image file + * @param rEye The Eye object associated with the image + */ + void EOC2Manager::processLR(const string &rFileName, EOC2Eye &rEye) { + // Strings handle + EOC2StringUtils osu; + + // Load the image + rEye.loadOriginalImage(rFileName); + + + // Segment the iris + rEye.segment(mMinIrisDiameter, mMinPupilDiameter, mMaxIrisDiameter, + mMaxPupilDiameter); + + // Normalize the iris image (Cartesian -> polar) + rEye.normalize(mWidthOfNormalizedIris, mHeightOfNormalizedIris); + + // Extract features required for upright / upside-down classification + rEye.featuresLR(); + + // Since we are here (no exceptions thrown) the processing is succesful + rEye.bProcessedOK = true; + +#ifdef EOC2_DEBUG + // Save debug data - segmentation image, parameters, mask, normalized image + // and mask (not used in the final version) + + string short_name = rFileName.substr(0, rFileName.rfind(".")); + + if (mOutputDirSegmentedImages != "") { + rEye.saveSegmentedImage(mOutputDirSegmentedImages + short_name + + mSuffixSegmentedImages); + } + if (mOutputDirParameters != "") { + rEye.saveParameters(mOutputDirParameters + short_name + + mSuffixParameters); + } + if (mOutputDirMasks != "") { + rEye.saveMask(mOutputDirMasks + short_name + mSuffixMasks); + } + if (mOutputDirNormalizedImages != "") { + rEye.saveNormalizedImage(mOutputDirNormalizedImages + short_name + + mSuffixNormalizedImages); + } + if (mOutputDirNormalizedMasks != "") { + rEye.saveNormalizedMask(mOutputDirNormalizedMasks + short_name + + mSuffixNormalizedMasks); + } +#endif + } // end of function + + /** + * Determines whether the eye in the image is upright or upside-down. + * 0 = Upright, 1 = Upside down, -1 = Unable to determine. + * + * @param image_path The local path to the image file + */ + int EOC2Manager::recognitionUD(std::string image_path, int image_type, + int image_width, int image_height) { + EOC2Eye eyeGlobal; + + if (!keep_class_in_mem) { + loadClassifier_UD(); + } + + // Class prediction and presentation of the result + Mat testingData(1, eyeGlobal.getNumberOfUDfeatures(), CV_32FC1); +#ifdef EOC2_DEBUG + eyeGlobal.printFeaturesUDHeader(); +#endif + + int recogOrientation = -1; + + EOC2Eye eye(image_type, image_width, image_height); + + try { + LOG4CXX_DEBUG(logger, "Processing image..."); + processUD(image_path, eye); + } catch (exception &e) { + LOG4CXX_ERROR(logger, "Error while processing image: " << e.what() << + ". " << image_path << "\t\t" << "UNDETERMINED"); + } + if (eye.bProcessedOK) { + testingData.at(0) = (eye.stFeaturesUD.fEyelidCoverage - + static_cast(colMEAN_UD->at(0))) / + static_cast(colSTD_UD->at(0)); + testingData.at(1) = (eye.stFeaturesUD.fEyelidValue - + static_cast(colMEAN_UD->at(1))) / + static_cast(colSTD_UD->at(1)); + testingData.at(2) = (eye.stFeaturesUD.fVerticalPupilShift - + static_cast(colMEAN_UD->at(2))) / + static_cast(colSTD_UD->at(2)); + testingData.at(3) = (eye.stFeaturesUD.fEyelidFiltered - + static_cast(colMEAN_UD->at(3))) / + static_cast(colSTD_UD->at(3)); + testingData.at(4) = (eye.stFeaturesUD.fEyelidFilteredCosVar - + static_cast(colMEAN_UD->at(4))) / + static_cast(colSTD_UD->at(4)); + testingData.at(5) = (eye.stFeaturesUD.fEyelidFilteredSinVar - + static_cast(colMEAN_UD->at(5))) / + static_cast(colSTD_UD->at(5)); + + if (SVM_UD->predict(testingData) > 0) { +#ifdef EOC2_DEBUG + eye.printFeaturesUD(i, image_path, "UPRIGHT"); +#endif + recogOrientation = 0; + LOG4CXX_DEBUG(logger, "Detected UPRIGHT EYE"); + } else { +#ifdef EOC2_DEBUG + eye.printFeaturesUD(i, image_path, "UP/DOWN"); +#endif + recogOrientation = 1; + LOG4CXX_DEBUG(logger, "Detected UPSIDE DOWN EYE"); + } + } + + if (!keep_class_in_mem) { + cleanupClassifier_UD(); + } + + return recogOrientation; + } + + /** + * Determines whether the image is of the left or right eye. 0 = Left eye, + * 1 = Right eye, -1 = Unable to determine. + * + * @param image_path The local path to the image file + */ + int EOC2Manager::recognitionLR(std::string image_path, int image_type, + int image_width, int image_height) { + EOC2Eye eyeGlobal; + if (!keep_class_in_mem) { + loadClassifier_LR(); + } + + // Class prediction and presentation of the result + Mat testingData(1, eyeGlobal.getNumberOfLRfeatures(), CV_32FC1); +#ifdef EOC2_DEBUG + eyeGlobal.printFeaturesLRHeader(); +#endif + + int recogOrientation = -1; + + EOC2Eye eye(image_type, image_width, image_height); + + try { + LOG4CXX_DEBUG(logger, "Processing image..."); + processLR(image_path, eye); + } catch (exception &e) { + LOG4CXX_ERROR(logger, "Error while processing image: " << e.what() << + ". " << image_path << "\t\t" << "UNDETERMINED"); + } + if (eye.bProcessedOK) { + testingData.at(0) = (eye.stFeaturesLR.fEyelidValue - + static_cast(colMEAN_LR->at(0))) / + static_cast(colSTD_LR->at(0)); + testingData.at(1) = (eye.stFeaturesLR.fHorizontalPupilShift - + static_cast(colMEAN_LR->at(1))) / + static_cast(colSTD_LR->at(1)); + + if (SVM_LR->predict(testingData) > 0) { +#ifdef EOC2_DEBUG + eye.printFeaturesLR(i, image_path, "LEFT"); +#endif + recogOrientation = 0; + LOG4CXX_DEBUG(logger, "Detected LEFT EYE"); + } else { +#ifdef EOC2_DEBUG + eye.printFeaturesLR(i, image_path, "RIGHT"); +#endif + recogOrientation = 1; + LOG4CXX_DEBUG(logger, "Detected RIGHT EYE"); + } + } + + if (!keep_class_in_mem) { + cleanupClassifier_LR(); + } + return recogOrientation; + } + + + + /** + * Run the software according to the configuration. + * + * @param image_path The local path to the image file + * @param up_down An integer reference to be set to the determined image + * orientation, upright or upside down. 0 = Eye is upright, 1 = Eye is + * upside-down, -1 = Unable to determine. + * @param left_right An integer reference to be set to the determined + * left/right eye classification. 0 = Left eye, 1 = Right eye, + * -1 = Unable to determine + */ + void EOC2Manager::run(std::string image_path, int &up_down, int &left_right, + int image_type, int image_width, int image_height) { + up_down = -1; + left_right = -1; + + try { + LOG4CXX_INFO(logger, "Starting UP / DOWN classification..."); + // Run the recognition + up_down = recognitionUD(image_path, image_type, image_width, + image_height); + LOG4CXX_INFO(logger, "Finshed UP / DOWN classification"); + } catch (exception &e) { + LOG4CXX_ERROR(logger, "Error occured duing UP / DOWN classification: " + << e.what()); + } + + try { + LOG4CXX_INFO(logger, "Starting LEFT / RIGHT classification..."); + // Run the recognition + left_right = recognitionLR(image_path, image_type, image_width, + image_height); + LOG4CXX_INFO(logger, "Finshed LEFT / RIGHT classification"); + } catch (exception &e) { + LOG4CXX_ERROR(logger, "Error occured duing LEFT / RIGHT classification: " + << e.what()); + } + } // end of function +} // namespace EOC2 diff --git a/acii/acii-client/src/EOC2Processings.cpp b/acii/acii-client/src/EOC2Processings.cpp new file mode 100755 index 0000000..d94529a --- /dev/null +++ b/acii/acii-client/src/EOC2Processings.cpp @@ -0,0 +1,1174 @@ +/***************************************************************************** + * Eye Orientation CLassifier v2 (U/D + L/R) + * Author: Adam Czajka, aczajka@nd.edu, May 2016 + * + * Code in this file is partially based on Open Source for Iris (OSIRIS) v. 4.1 + * under BSD license http://opensource.org/licenses/bsd-license.php + ******************************************************************************/ + + +#include +#include +#include +#include +#include +#include +#include "EOC2Utils.h" +#include "EOC2Processings.h" + +using std::invalid_argument; +using std::max; +using std::min; +using std::string; +using std::vector; + +const extern log4cxx::LoggerPtr logger; + +namespace EOC2 { + + EOC2Processings::EOC2Processings() { + // Do nothing + } + + EOC2Processings::~EOC2Processings() { + // Do nothing + } + + void EOC2Processings::segment(const IplImage * pSrc, + IplImage * pMask, + EOC2Circle & rPupil, + EOC2Circle & rIris, + vector & rThetaCoarsePupil, + vector & rThetaCoarseIris, + vector & rCoarsePupilContour, + vector & rCoarseIrisContour, + int minIrisDiameter, + int minPupilDiameter, + int maxIrisDiameter, + int maxPupilDiameter) { + ////////////////// + // Check arguments + + // String functions + EOC2StringUtils str; + + // Temporary int to check sizes of pupil and iris + int check_size = 0; + + // Default value for maxIrisDiameter if user did not specify it + if (maxIrisDiameter == 0) { + maxIrisDiameter = min(pSrc->height, pSrc->width); + } else if (maxIrisDiameter > (check_size = + static_cast(floor(static_cast(min(pSrc->height, pSrc->width)))))) { + // Change maxIrisDiameter if it is too big relative to image sizes + LOG4CXX_WARN(logger, + "Warning in function segment : maxIrisDiameter = " << + maxIrisDiameter << + " is replaced by " << check_size << + " because image size is " << pSrc->width << "x" << pSrc->height); + + maxIrisDiameter = check_size; + } + + // Default value for maxPupilDiameter if user did not specify it + if (maxPupilDiameter == 0) { + maxPupilDiameter = + static_cast(OSI_MAX_RATIO_PUPIL_IRIS * maxIrisDiameter); + } else if (maxPupilDiameter > (check_size = + static_cast(OSI_MAX_RATIO_PUPIL_IRIS * maxIrisDiameter))) { + // Change maxPupilDiameter if it is too big relative to maxIrisDiameter + // and OSI_MAX_RATIO_PUPIL_IRIS + LOG4CXX_WARN(logger, + "Warning in function segment : maxPupilDiameter = " << + maxPupilDiameter << + " is replaced by " << check_size << + " because maxIrisDiameter = " << maxIrisDiameter << + " and ratio pupil/iris is generally lower than " << + OSI_MAX_RATIO_PUPIL_IRIS); + maxPupilDiameter = check_size; + } + + // Change minIrisDiameter if it is too small relative to OSI_SMALLEST_IRIS + if (minIrisDiameter < (check_size = OSI_SMALLEST_IRIS)) { + LOG4CXX_WARN(logger, + "Warning in function segment : minIrisDiameter = " << + minIrisDiameter << + " is replaced by " << check_size << + " which is the smallest size for detecting iris"); + minIrisDiameter = check_size; + } + + // Change minPupilDiameter if it is too small relative to minIrisDiameter + // and OSI_MIN_RATIO_PUPIL_IRIS + if (minPupilDiameter < (check_size = + static_cast(minIrisDiameter*OSI_MIN_RATIO_PUPIL_IRIS))) { + LOG4CXX_WARN(logger, + "Warning in function segment : minPupilDiameter = " << + minPupilDiameter << + " is replaced by " << check_size << + " because minIrisDiameter = " << minIrisDiameter << + " and ratio pupil/iris is generally upper than " << + OSI_MIN_RATIO_PUPIL_IRIS); + minIrisDiameter = check_size; + } + + // Check that minIrisDiameter < maxIrisDiameter + if (minIrisDiameter > maxIrisDiameter) { + throw invalid_argument("Error in function segment : minIrisDiameter = " + + str.toString(minIrisDiameter) + + " should be lower than maxIrisDiameter = " + + str.toString(maxIrisDiameter)); + } + + // Make size odds + minIrisDiameter += (minIrisDiameter % 2) ? 0 : -1; + maxIrisDiameter += (maxIrisDiameter % 2) ? 0 : +1; + minPupilDiameter += (minPupilDiameter % 2) ? 0 : -1; + maxPupilDiameter += (maxPupilDiameter % 2) ? 0 : +1; + + /////////////////// + // Start processing + + // Locate the pupil + detectPupil(pSrc, rPupil, minPupilDiameter, maxPupilDiameter); + + // Fill the holes in an area surrounding pupil + IplImage * clone_src = cvCloneImage(pSrc); + cvSetImageROI(clone_src, cvRect( + static_cast( + rPupil.getCenter().x - 3.0 / 4.0 * maxIrisDiameter / 2.0), + static_cast( + rPupil.getCenter().y - 3.0 / 4.0 * maxIrisDiameter / 2.0), + static_cast(3.0 / 4.0 * maxIrisDiameter), + static_cast(3.0 / 4.0 * maxIrisDiameter))); + fillWhiteHoles(clone_src, clone_src); + cvResetImageROI(clone_src); + + // Will contain samples of angles, in radians + vector theta; + float theta_step = 0; + + ///////////////////////// + // Pupil Accurate Contour + + theta.clear(); + theta_step = static_cast(360.0 / OSI_PI / rPupil.getRadius()); + for (float t = 0; t < 360; t += theta_step) { + theta.push_back(t*OSI_PI/180); + } + vector pupil_accurate_contour = findContour(clone_src, + rPupil.getCenter(), + theta, + rPupil.getRadius()-20, + rPupil.getRadius()+20); + + // Circle fitting on accurate contour + rPupil.computeCircleFittingFloat(pupil_accurate_contour); + + /////////////////////// + // Pupil Coarse Contour + + theta.clear(); + theta_step = static_cast(360.0 / OSI_PI / rPupil.getRadius() * 2); + for (float t = 0; t < 360; t += theta_step) { + if (t > 45 && t < 135) t += theta_step; + theta.push_back(t*OSI_PI/180); + } + vector pupil_coarse_contour = findContour(clone_src, + rPupil.getCenter(), + theta, + rPupil.getRadius()-20, + rPupil.getRadius()+20); + + rThetaCoarsePupil = theta; + rCoarsePupilContour = pupil_coarse_contour; + + // Circle fitting on coarse contour + rPupil.computeCircleFittingFloat(pupil_coarse_contour); + + //////////////// + // Mask of pupil + + IplImage * mask_pupil = cvCloneImage(pSrc); + cvZero(mask_pupil); + drawContour(mask_pupil, pupil_accurate_contour, cvScalar(255), -1); + + ////////////////////// + // Iris Coarse Contour + + theta.clear(); + int min_radius = max( + static_cast(rPupil.getRadius() / OSI_MAX_RATIO_PUPIL_IRIS), + static_cast(minIrisDiameter / 2)); + int max_radius = min( + static_cast(rPupil.getRadius() / OSI_MIN_RATIO_PUPIL_IRIS), + static_cast(3 * maxIrisDiameter / 4)); + theta_step = static_cast(360.0 / OSI_PI / min_radius); + + for (float t = 0; t < 360; t += theta_step) { + if (t < 180 || (t > 225 && t < 315)) t += 2*theta_step; + theta.push_back(t*OSI_PI/180); + } + + vector iris_coarse_contour = findContour(clone_src, + rPupil.getCenter(), + theta, + min_radius, + max_radius); + + rThetaCoarseIris = theta; + rCoarseIrisContour = iris_coarse_contour; + + // Circle fitting on coarse contour + rIris.computeCircleFittingFloat(iris_coarse_contour); + + // Mask of iris + /////////////// + + IplImage * mask_iris = cvCloneImage(mask_pupil); + cvZero(mask_iris); + drawContour(mask_iris, iris_coarse_contour, cvScalar(255), -1); + + //////////////////////// + // Iris Accurate Contour + + // For iris accurate contour, limit the search of contour inside a mask + + // Dilate mask of iris by a disk-shape element + IplImage * mask_iris2 = cvCloneImage(mask_iris); + IplConvKernel * struct_element = + cvCreateStructuringElementEx(21, 21, 10, 10, CV_SHAPE_ELLIPSE); + cvDilate(mask_iris2, mask_iris2, struct_element); + cvReleaseStructuringElement(&struct_element); + + // Dilate the mask of pupil by a horizontal line-shape element + IplImage * mask_pupil2 = cvCloneImage(mask_pupil); + struct_element = cvCreateStructuringElementEx(21, 21, 10, 1, CV_SHAPE_RECT); + cvDilate(mask_pupil2, mask_pupil2, struct_element); + cvReleaseStructuringElement(&struct_element); + cvXor(mask_iris2, mask_pupil2, mask_iris2); + + theta.clear(); + theta_step = static_cast(360.0 / OSI_PI / rIris.getRadius()); + for (float t = 0; t < 360; t += theta_step) { + theta.push_back(t*OSI_PI/180); + } + vector iris_accurate_contour = findContour(clone_src, + rPupil.getCenter(), + theta, + rIris.getRadius()-50, + rIris.getRadius()+20, + mask_iris2); + + // Release memory + cvReleaseImage(&mask_pupil2); + cvReleaseImage(&mask_iris2); + + ////////////////////////////////////////// + // Mask of iris based on accurate contours + + cvZero(mask_iris); + drawContour(mask_iris, iris_accurate_contour, cvScalar(255), -1); + cvXor(mask_iris, mask_pupil, mask_iris); + + ///////////////////////////////////////// + // Refine the mask by removing some noise + + // Build a safe area = avoid occlusions + IplImage * safe_area = cvCloneImage(mask_iris); + cvRectangle(safe_area, + cvPoint(0, 0), + cvPoint(safe_area->width - 1, rPupil.getCenter().y), + cvScalar(0), -1); + cvRectangle(safe_area, + cvPoint(0, rPupil.getCenter().y + rPupil.getRadius()), + cvPoint(safe_area->width - 1, safe_area->height - 1), + cvScalar(0), -1); + struct_element = + cvCreateStructuringElementEx(11, 11, 5, 5, CV_SHAPE_ELLIPSE); + cvErode(safe_area, safe_area, struct_element); + cvReleaseStructuringElement(&struct_element); + + // Compute the mean and the variance of iris texture inside safe area + double iris_mean = cvMean(pSrc, safe_area); + IplImage * variance = cvCreateImage(cvGetSize(pSrc), IPL_DEPTH_32F, 1); + cvConvert(pSrc, variance); + cvSubS(variance, cvScalar(iris_mean), variance, safe_area); + cvMul(variance, variance, variance); + double iris_variance = sqrt(cvMean(variance, safe_area)); + cvReleaseImage(&variance); + cvReleaseImage(&safe_area); + + // Build mask of noise : |I-mean| > 2.35 * variance + IplImage * mask_noise = cvCloneImage(pSrc); + cvAbsDiffS(pSrc, mask_noise, cvScalar(iris_mean)); + cvThreshold(mask_noise, mask_noise, 2.35 * iris_variance, 255, + CV_THRESH_BINARY); + cvAnd(mask_iris, mask_noise, mask_noise); + + // Fusion with accurate contours + IplImage * accurate_contours = cvCloneImage(mask_iris); + struct_element = cvCreateStructuringElementEx(3, 3, 1, 1, CV_SHAPE_ELLIPSE); + cvMorphologyEx(accurate_contours, accurate_contours, accurate_contours, + struct_element, CV_MOP_GRADIENT); + cvReleaseStructuringElement(&struct_element); + reconstructMarkerByMask(accurate_contours, mask_noise, mask_noise); + cvReleaseImage(&accurate_contours); + cvXor(mask_iris, mask_noise, pMask); + + // Release memory + cvReleaseImage(&mask_noise); + cvReleaseImage(&mask_pupil); + cvReleaseImage(&mask_iris); + cvReleaseImage(&clone_src); // AC added + } // end of function + + + + + void EOC2Processings::normalize(const IplImage * pSrc , + IplImage * pDst , + const EOC2Circle & rPupil , + const EOC2Circle & rIris) { + // Local variables + CvPoint point_pupil , point_iris; + int x , y; + float theta , radius; + + // Set to zeros all pixels + cvZero(pDst); + + // Loop on columns of normalized src + for (int j = 0; j < pDst->width; j++) { + // One column correspond to an angle teta + theta = static_cast(j) / pDst->width * 2 * OSI_PI; + + // Coordinates relative to both centers : iris and pupil + point_pupil = convertPolarToCartesian(rPupil.getCenter(), + rPupil.getRadius(), theta); + point_iris = convertPolarToCartesian(rIris.getCenter(), + rIris.getRadius(), theta); + + // Loop on lines of normalized src + for (int i = 0; i < pDst->height; i++) { + // The radial parameter + radius = static_cast(i) / pDst->height; + + // Coordinates relative to both radii : iris and pupil + x = static_cast((1-radius) * + point_pupil.x + radius * point_iris.x); + y = static_cast((1-radius) * + point_pupil.y + radius * point_iris.y); + + // Do not exceed src size + if (x >= 0 && x < pSrc->width && y >= 0 && y < pSrc->height) { + ((uchar*)(pDst->imageData+i*pDst->widthStep))[j] = + ((uchar*)(pSrc->imageData+y*pSrc->widthStep))[x]; + } + } + } + } + + + void EOC2Processings::normalizeFromContour(const IplImage * pSrc , + IplImage * pDst , + const EOC2Circle & rPupil , + const EOC2Circle & rIris , + const vector rThetaCoarsePupil , + const vector rThetaCoarseIris , + const vector & rPupilCoarseContour , + const vector & rIrisCoarseContour) { + // Local variables + CvPoint point_pupil , point_iris; + int x , y; + float theta , radius; + + // Set to zeros all pixels + cvZero(pDst); + + // Loop on columns of normalized src + for (int j = 0; j < pDst->width; j++) { + // One column correspond to an angle teta + theta = static_cast(j) / pDst->width * 2 * OSI_PI; + + // Interpolate pupil and iris radii from coarse contours + point_pupil = interpolate(rPupilCoarseContour, rThetaCoarsePupil, theta); + point_iris = interpolate(rIrisCoarseContour, rThetaCoarseIris, theta); + + // Loop on lines of normalized src + for (int i = 0; i < pDst->height; i++) { + // The radial parameter + radius = static_cast(i) / pDst->height; + + // Coordinates relative to both radii : iris and pupil + x = static_cast((1-radius) * + point_pupil.x + radius * point_iris.x); + y = static_cast((1-radius) * + point_pupil.y + radius * point_iris.y); + + // Do not exceed src size + if (x >= 0 && x < pSrc->width && y >= 0 && y < pSrc->height) { + ((uchar*)(pDst->imageData+i*pDst->widthStep))[j] = + ((uchar*)(pSrc->imageData+y*pSrc->widthStep))[x]; + } + } + } + } + + CvPoint EOC2Processings::interpolate(const vector coarseContour , + const vector coarseTheta , + const float theta) { + float interpolation; + int i1 , i2; + + if (theta < coarseTheta[0]) { + i1 = coarseTheta.size() - 1; + i2 = 0; + interpolation = (theta - (coarseTheta[i1]-2*OSI_PI)) / + (coarseTheta[i2] - (coarseTheta[i1]-2*OSI_PI)); + } else if (theta >= coarseTheta[coarseTheta.size()-1]) { + i1 = coarseTheta.size() - 1; + i2 = 0; + interpolation = (theta - coarseTheta[i1]) / + (coarseTheta[i2]+2*OSI_PI - coarseTheta[i1]); + } else { + int i = 0; + while (coarseTheta[i+1] <= theta) i++; + i1 = i; + i2 = i+1; + interpolation = (theta - coarseTheta[i1]) / + (coarseTheta[i2] - coarseTheta[i1]); + } + + float x = (1-interpolation) * coarseContour[i1].x + + interpolation * coarseContour[i2].x; + float y = (1-interpolation) * coarseContour[i1].y + + interpolation * coarseContour[i2].y; + + return cvPoint(static_cast(x), static_cast(y)); + } + + // Convert polar coordinates into cartesian coordinates + CvPoint EOC2Processings::convertPolarToCartesian(const CvPoint &rCenter, + int rRadius, + float rTheta) { + int x = static_cast(rCenter.x + rRadius * cos(rTheta)); + int y = static_cast(rCenter.y - rRadius * sin(rTheta)); + return cvPoint(x, y); + } + + + // Add left and right borders on an unwrapped image + IplImage * EOC2Processings::addBorders(const IplImage * pImage , + int width) { + // Result image + IplImage * result = cvCreateImage(cvSize(pImage->width + 2 * width, + pImage->height), + pImage->depth, + pImage->nChannels); + + // Copy the image in the center + cvCopyMakeBorder(pImage, result, cvPoint(width, 0), IPL_BORDER_REPLICATE, + cvScalarAll(0)); + + // Create the borders left and right assuming wrapping + for (int i = 0; i < pImage->height; i++) { + for (int j = 0; j < width; j++) { + ((uchar *)( + result->imageData + i*result->widthStep))[j] = + ((uchar *)( + pImage->imageData + i*pImage->widthStep))[pImage->width-width+j]; + + ((uchar *)( + result->imageData + i*result->widthStep))[result->width-width+j] = + ((uchar *) + (pImage->imageData + i*pImage->widthStep))[j]; + } + } + + return result; + } + + + // Detect and locate a pupil inside an eye image + void EOC2Processings::detectPupil(const IplImage * pSrc, + EOC2Circle & rPupil, + int minPupilDiameter, + int maxPupilDiameter) { + ////////////////// + // Check arguments + + // String functions + EOC2StringUtils str; + + // Default value for maxPupilDiameter, if user did not specify it + if (maxPupilDiameter == 0) { + maxPupilDiameter = static_cast( + min(pSrc->height, pSrc->width) * OSI_MAX_RATIO_PUPIL_IRIS); + } else if (maxPupilDiameter > + min(pSrc->height, pSrc->width) * OSI_MAX_RATIO_PUPIL_IRIS) { + // Change maxPupilDiameter if it is too big relative to the image size and + // the ratio pupil/iris + int newmaxPupilDiameter = + static_cast(floor( + min(pSrc->height, pSrc->width) * OSI_MAX_RATIO_PUPIL_IRIS)); + LOG4CXX_WARN(logger, + "Warning in function detectPupil : maxPupilDiameter = " << + maxPupilDiameter << + " is replaced by " << newmaxPupilDiameter << + " because image size is " << pSrc->width << "x" << pSrc->height << + " and ratio pupil/iris is generally lower than " << + OSI_MAX_RATIO_PUPIL_IRIS); + maxPupilDiameter = newmaxPupilDiameter; + } + + // Change minPupilDiameter if it is too small relative to OSI_SMALLEST_PUPIL + if (minPupilDiameter < OSI_SMALLEST_PUPIL) { + LOG4CXX_WARN(logger, + "Warning in function detectPupil : minPupilDiameter = " << + minPupilDiameter << + " is replaced by " << OSI_SMALLEST_PUPIL << + " which is the smallest size for detecting pupil"); + minPupilDiameter = OSI_SMALLEST_PUPIL; + } + + // Check that minPupilDiameter < maxPupilDiameter + if (minPupilDiameter >= maxPupilDiameter) { + throw invalid_argument( + "Error in function detectPupil : minPupilDiameter = " + + str.toString(minPupilDiameter) + + " should be lower than maxPupilDiameter = " + + str.toString(maxPupilDiameter)); + } + + /////////////////// + // Start processing + + // Resize image (downsample) + float scale = static_cast(OSI_SMALLEST_PUPIL) / minPupilDiameter; + IplImage * resized = cvCreateImage(cvSize( + static_cast(pSrc->width*scale), + static_cast(pSrc->height*scale)), + pSrc->depth, 1); + cvResize(pSrc, resized); + + // Rescale sizes + maxPupilDiameter = static_cast(maxPupilDiameter * scale); + minPupilDiameter = static_cast(minPupilDiameter * scale); + + // Make sizes odd + maxPupilDiameter += (maxPupilDiameter % 2) ? 0 : +1; + minPupilDiameter += (minPupilDiameter % 2) ? 0 : -1; + + // Fill holes + IplImage * filled = cvCreateImage(cvGetSize(resized), resized->depth, 1); + fillWhiteHoles(resized, filled); + + // Gradients in horizontal direction + IplImage * gh = cvCreateImage(cvGetSize(filled), IPL_DEPTH_32F, 1); + cvSobel(filled, gh, 1, 0); + + // Gradients in vertical direction + IplImage * gv = cvCreateImage(cvGetSize(filled), IPL_DEPTH_32F, 1); + cvSobel(filled, gv, 0, 1); + + // Normalize gradients + IplImage * gh2 = cvCreateImage(cvGetSize(filled), IPL_DEPTH_32F, 1); + cvMul(gh, gh, gh2); + IplImage * gv2 = cvCreateImage(cvGetSize(filled), IPL_DEPTH_32F, 1); + cvMul(gv, gv, gv2); + IplImage * gn = cvCreateImage(cvGetSize(filled), IPL_DEPTH_32F, 1); + cvAdd(gh2, gv2, gn); + cvPow(gn, gn, 0.5); + cvDiv(gh, gn, gh); + cvDiv(gv, gn, gv); + + // Create the filters fh and fv + int filter_size = maxPupilDiameter; + filter_size += (filter_size % 2) ? 0 : -1; + CvMat * fh = cvCreateMat(filter_size, filter_size, CV_32FC1); + CvMat * fv = cvCreateMat(filter_size, filter_size, CV_32FC1); + + for (int i = 0; i < fh->rows; i++) { + float x = static_cast(i) - (filter_size-1)/2; + + for (int j = 0; j < fh->cols; j++) { + float y = static_cast(j) - (filter_size-1)/2; + if (x != 0 || y != 0) { + (fh->data.fl)[i*fh->cols+j] = y / sqrt(x*x+y*y); + (fv->data.fl)[i*fv->cols+j] = x / sqrt(x*x+y*y); + } else { + (fh->data.fl)[i*fh->cols+j] = 0; + (fv->data.fl)[i*fv->cols+j] = 0; + } + } + } + + // Create the mask + CvMat * mask = cvCreateMat(filter_size, filter_size, CV_8UC1); + + // Temporary matrix for masking the filter + CvMat * temp_filter = cvCreateMat(filter_size, filter_size, CV_32FC1); + + double old_max_val = 0; + + // Multi resolution of radius + for (int r = (OSI_SMALLEST_PUPIL-1)/2; r < (maxPupilDiameter-1)/2; r++) { + // Centred ring with radius = r and width = 2 + cvZero(mask); + cvCircle(mask, cvPoint((filter_size-1)/2, (filter_size-1)/2), + r, cvScalar(1), 2); + + // Fh * Gh + cvZero(temp_filter); + cvCopy(fh, temp_filter, mask); + cvFilter2D(gh, gh2, temp_filter); + + // Fv * Gv + cvZero(temp_filter); + cvCopy(fv, temp_filter, mask); + cvFilter2D(gv, gv2, temp_filter); + + // Fh*Gh + Fv*Gv + cvAdd(gh2, gv2, gn); + cvScale(gn, gn, 1.0/cvSum(mask).val[0]); + + // Sum in the disk-shaped neighbourhood + cvZero(mask); + cvCircle(mask, cvPoint((filter_size-1)/2, (filter_size-1)/2), + r, cvScalar(1), -1); + cvFilter2D(filled, gh2, mask); + cvScale(gh2, gh2, -1.0/cvSum(mask).val[0]/255.0, 1); + + // Add the two features : contour + darkness + cvAdd(gn, gh2, gn); + + // Find the maximum in feature image + double max_val; + CvPoint max_loc; + cvMinMaxLoc(gn, 0, &max_val, 0, &max_loc); + + if (max_val > old_max_val) { + old_max_val = max_val; + rPupil.setCircle(max_loc, r); + } + } + + // Rescale circle + int x = static_cast( + (static_cast(rPupil.getCenter().x * (pSrc->width-1))) / + (filled->width-1) + + static_cast((1.0/scale)-1)/2); + int y = static_cast( + (static_cast( + rPupil.getCenter().y * (pSrc->height-1))) / (filled->height-1) + + static_cast((1.0/scale)-1)/2); + int r = static_cast(rPupil.getRadius() / scale); + rPupil.setCircle(x, y, r); + + // Release memory + cvReleaseImage(&resized); + cvReleaseImage(&filled); + cvReleaseImage(&gh); + cvReleaseImage(&gv); + cvReleaseImage(&gh2); + cvReleaseImage(&gv2); + cvReleaseImage(&gn); + cvReleaseMat(&fh); + cvReleaseMat(&fv); + cvReleaseMat(&mask); + cvReleaseMat(&temp_filter); + } // end of function + + // Morphological reconstruction + void EOC2Processings::reconstructMarkerByMask(const IplImage * pMarker, + const IplImage * pMask, + IplImage * pDst) { + // Temporary image that will inform about marker evolution + IplImage * difference = cvCloneImage(pMask); + + // :WARN: if user calls f(x, y, y) instead of f(x, y, z), the mask MUST + // be cloned before processing + IplImage * mask = cvCloneImage(pMask); + + // Copy the marker + cvCopy(pMarker, pDst); + + // Structuring element for morphological operation + IplConvKernel * structuring_element = + cvCreateStructuringElementEx(3, 3, 1, 1, CV_SHAPE_ELLIPSE); + + // Will stop when marker does not change anymore + while (cvSum(difference).val[0]) { + // Remind marker before processing, in order to + // compare with the marker after processing + cvCopy(pDst, difference); + + // Dilate the marker + cvDilate(pDst, pDst, structuring_element); + + // Keep the minimum between marker and mask + cvMin(pDst, mask, pDst); + + // Evolution of the marker + cvAbsDiff(pDst, difference, difference); + } + + // Release memory + cvReleaseImage(&mask); + cvReleaseImage(&difference); + cvReleaseStructuringElement(&structuring_element); + } // end of function + + // Fill the white holes surrounded by dark pixels, such as specular reflection + // inside pupil area + void EOC2Processings::fillWhiteHoles(const IplImage * pSrc, + IplImage * pDst) { + int width , height; + if (pSrc->roi) { + width = pSrc->roi->width; + height = pSrc->roi->height; + } else { + width = pSrc->width; + height = pSrc->height; + } + + // Mask for reconstruction : pSrc + borders=0 + IplImage * mask = cvCreateImage(cvSize(width+2, height+2), pSrc->depth, 1); + cvZero(mask); + cvSetImageROI(mask, cvRect(1, 1, width, height)); + cvCopy(pSrc, mask); + cvResetImageROI(mask); + + // Marker for reconstruction : all=0 + borders=255 + IplImage * marker = cvCloneImage(mask); + cvZero(marker); + cvRectangle(marker, cvPoint(1, 1), cvPoint(width+1, height+1), + cvScalar(255)); + + // Temporary result of reconstruction + IplImage * result = cvCloneImage(mask); + + // Morphological reconstruction + reconstructMarkerByMask(marker, mask, result); + + // Remove borders + cvSetImageROI(result, cvRect(1, 1, width, height)); + cvCopy(result, pDst); + + // Release memory + cvReleaseImage(&marker); + cvReleaseImage(&mask); + cvReleaseImage(&result); + } // end of function + + // Rescale between 0 and 255, and show image + void EOC2Processings::showImage(const IplImage *pImage, + int delay, + const string &rWindowName) { + IplImage * show; + + if (pImage->nChannels == 1) { + // Rescale between 0 and 255 by computing : (X-min)/(max-min) + double min_val , max_val; + cvMinMaxLoc(pImage, &min_val, &max_val); + IplImage * scaled = cvCloneImage(pImage); + cvScale(pImage, scaled, 255/(max_val-min_val), + -min_val/(max_val-min_val)); + + // Convert into 8-bit + show = cvCreateImage(cvGetSize(pImage), IPL_DEPTH_8U, 1); + cvConvert(scaled, show); + + // Release memory + cvReleaseImage(&scaled); + } else { + show = cvCloneImage(pImage); + } + + // Show image + cvShowImage(rWindowName.c_str(), show); + cvWaitKey(delay); + + // Release image + cvReleaseImage(&show); + } + + // Unwrap a ring into a rectangular band + IplImage * EOC2Processings::unwrapRing(const IplImage *pSrc, + const CvPoint &rCenter, + int minRadius, + int maxRadius, + const vector &rTheta) { + // Result image + IplImage * result = cvCreateImage(cvSize( + rTheta.size(), maxRadius-minRadius+1), pSrc->depth, 1); + cvZero(result); + + // Loop on columns of normalized image + for (int j = 0; j < result->width; j++) { + // Loop on lines of normalized image + for (int i = 0; i < result->height; i++) { + CvPoint point = convertPolarToCartesian(rCenter, + minRadius+i, rTheta[j]); + + // Do not exceed image size + if (point.x >= 0 && point.x < pSrc->width && + point.y >= 0 && point.y < pSrc->height) { + ((uchar *)(result->imageData+i*result->widthStep))[j] = + ((uchar *)(pSrc->imageData+point.y*pSrc->widthStep))[point.x]; + } + } + } + return result; + } + + // Smooth the image by anisotropic smoothing (Gross & Brajovic, 2003) + void EOC2Processings::processAnisotropicSmoothing(const IplImage * pSrc , + IplImage * pDst , + int iterations , + float lambda) { + // Temporary float images + IplImage * tfs = cvCreateImage(cvGetSize(pSrc), IPL_DEPTH_32F, 1); + cvConvert(pSrc, tfs); + IplImage * tfd = cvCreateImage(cvGetSize(pSrc), IPL_DEPTH_32F, 1); + cvConvert(pSrc, tfd); + + // Make borders dark + cvRectangle(tfd, cvPoint(0, 0), cvPoint(tfd->width-1, + tfd->height-1), cvScalar(0)); + + // Weber coefficients + float rhon , rhos , rhoe , rhow; + + // Store pixel values + float tfsc , tfsn , tfss , tfse , tfsw , tfdn , tfds , tfde , tfdw; + + // Loop on iterations + for (int k = 0; k < iterations; k++) { + // Odd pixels + for (int i = 1; i < tfs->height-1; i++) { + for (int j = 2-i%2; j < tfs->width-1; j = j + 2) { + // Get pixels in neighbourhood of original image + tfsc = (reinterpret_cast( + tfs->imageData + i * tfs->widthStep))[j]; + tfsn = (reinterpret_cast( + tfs->imageData + (i-1) * tfs->widthStep))[j]; + tfss = (reinterpret_cast( + tfs->imageData + (i+1) * tfs->widthStep))[j]; + tfse = (reinterpret_cast( + tfs->imageData + i * tfs->widthStep))[j-1]; + tfsw = (reinterpret_cast( + tfs->imageData + i * tfs->widthStep))[j+1]; + + // Get pixels in neighbourhood of light image + tfdn = (reinterpret_cast( + tfd->imageData + (i-1) * tfd->widthStep))[j]; + tfds = (reinterpret_cast( + tfd->imageData + (i+1) * tfd->widthStep))[j]; + tfde = (reinterpret_cast( + tfd->imageData + i * tfd->widthStep))[j-1]; + tfdw = (reinterpret_cast( + tfd->imageData + i * tfd->widthStep))[j+1]; + + // Compute weber coefficients + rhon = min(tfsn, tfsc) / max(1.0, abs(tfsn-tfsc)); + rhos = min(tfss, tfsc) / max(1.0, abs(tfss-tfsc)); + rhoe = min(tfse, tfsc) / max(1.0, abs(tfse-tfsc)); + rhow = min(tfsw, tfsc) / max(1.0, abs(tfsw-tfsc)); + + // Compute LightImage(i, j) + (reinterpret_cast(tfd->imageData+i*tfd->widthStep))[j] = + ((tfsc + lambda * + (rhon * tfdn + rhos * tfds + rhoe * tfde + rhow * tfdw)) / + (1 + lambda * (rhon + rhos + rhoe + rhow))); + } + } + + cvCopy(tfd, tfs); + + // Even pixels + for (int i = 1; i < tfs->height-1; i++) { + for (int j = 1+i%2; j < tfs->width-1; j = j + 2) { + // Get pixels in neighbourhood of original image + tfsc = (reinterpret_cast( + tfs->imageData + i * tfs->widthStep))[j]; + tfsn = (reinterpret_cast( + tfs->imageData + (i-1) * tfs->widthStep))[j]; + tfss = (reinterpret_cast( + tfs->imageData + (i+1) * tfs->widthStep))[j]; + tfse = (reinterpret_cast( + tfs->imageData + i * tfs->widthStep))[j-1]; + tfsw = (reinterpret_cast( + tfs->imageData + i * tfs->widthStep))[j+1]; + + // Get pixels in neighbourhood of light image + tfdn = (reinterpret_cast( + tfd->imageData + (i-1) * tfd->widthStep))[j]; + tfds = (reinterpret_cast( + tfd->imageData + (i+1) * tfd->widthStep))[j]; + tfde = (reinterpret_cast( + tfd->imageData + i * tfd->widthStep))[j-1]; + tfdw = (reinterpret_cast( + tfd->imageData + i * tfd->widthStep))[j+1]; + + // Compute weber coefficients + rhon = min(tfsn, tfsc) / max(1.0, abs(tfsn-tfsc)); + rhos = min(tfss, tfsc) / max(1.0, abs(tfss-tfsc)); + rhoe = min(tfse, tfsc) / max(1.0, abs(tfse-tfsc)); + rhow = min(tfsw, tfsc) / max(1.0, abs(tfsw-tfsc)); + + // Compute LightImage(i, j) + (reinterpret_cast(tfd->imageData+i*tfd->widthStep))[j] = + ((tfsc + lambda * + (rhon * tfdn + rhos * tfds + rhoe * tfde + rhow * tfdw)) + / (1 + lambda * (rhon + rhos + rhoe + rhow))); + } + } + + cvCopy(tfd, tfs); + cvConvert(tfd, pDst); + } // end of iterations k + + // Borders of image + for (int i = 0; i < tfd->height; i++) { + ((uchar*)(pDst->imageData+i*pDst->widthStep))[0] = + ((uchar*)(pDst->imageData+i*pDst->widthStep))[1]; + ((uchar*)(pDst->imageData+i*pDst->widthStep))[pDst->width-1] = + ((uchar*)(pDst->imageData+i*pDst->widthStep))[pDst->width-2]; + } + for (int j = 0; j < tfd->width; j++) { + ((uchar*)(pDst->imageData))[j] = + ((uchar*)(pDst->imageData+pDst->widthStep))[j]; + ((uchar*)(pDst->imageData+(pDst->height-1)*pDst->widthStep))[j] = + ((uchar*)(pDst->imageData+(pDst->height-2)*pDst->widthStep))[j]; + } + + // Release memory + cvReleaseImage(&tfs); + cvReleaseImage(&tfd); + } // end of function + + // Compute vertical gradients using Sobel operator + void EOC2Processings::computeVerticalGradients(const IplImage * pSrc, + IplImage * pDst) { + // Float values for Sobel + IplImage * result_sobel = cvCreateImage(cvGetSize(pSrc), IPL_DEPTH_32F, 1); + + // Sobel filter in vertical direction + cvSobel(pSrc, result_sobel, 0, 1); + + // Remove negative edges, ie from white (top) to black (bottom) + cvThreshold(result_sobel, result_sobel, 0, 0, CV_THRESH_TOZERO); + + // Convert into 8-bit + double min , max; + cvMinMaxLoc(result_sobel, &min, &max); + cvConvertScale(result_sobel, pDst, 255/(max-min), -255*min/(max-min)); + + // Release memory + cvReleaseImage(&result_sobel); + } // end of function + + // Run viterbi algorithm on gradient (or probability) image and find optimal + // path + void EOC2Processings::runViterbi(const IplImage *pSrc, + vector &rOptimalPath) { + // Initialize the output + rOptimalPath.clear(); + rOptimalPath.resize(pSrc->width); + + // Initialize cost matrix to zero + IplImage * cost = cvCreateImage(cvGetSize(pSrc), IPL_DEPTH_32F, 1); + cvZero(cost); + + // Forward process : build the cost matrix + for (int w = 0; w < pSrc->width; w++) { + for (int h = 0; h < pSrc->height; h++) { + // First column is same as source image + if (w == 0) { + (reinterpret_cast(cost->imageData+h*cost->widthStep))[w] = + ((uchar*)(pSrc->imageData+h*pSrc->widthStep))[w]; + } else { + // First line + if (h == 0) { + (reinterpret_cast( + cost->imageData + h * cost->widthStep))[w] = + max((reinterpret_cast( + cost->imageData+(h) * cost->widthStep))[w - 1], + (reinterpret_cast( + cost->imageData+(h+1) * cost->widthStep))[w - 1]) + + (reinterpret_cast( + pSrc->imageData + h * pSrc->widthStep))[w]; + } else if (h == pSrc->height - 1) { + // Last line + (reinterpret_cast( + cost->imageData + h * cost->widthStep))[w] = + max((reinterpret_cast( + cost->imageData + h * cost->widthStep))[w-1], + (reinterpret_cast( + cost->imageData + (h - 1)*cost->widthStep))[w-1]) + + (reinterpret_cast( + pSrc->imageData + h * pSrc->widthStep))[w]; + } else { + // Middle lines + (reinterpret_cast( + cost->imageData+h*cost->widthStep))[w] = + max((reinterpret_cast( + cost->imageData+h*cost->widthStep))[w-1], + max((reinterpret_cast( + cost->imageData+(h+1)*cost->widthStep))[w-1], + (reinterpret_cast( + cost->imageData+(h-1)*cost->widthStep))[w-1])) + + (reinterpret_cast( + pSrc->imageData+h*pSrc->widthStep))[w]; + } + } + } + } + + // Get the maximum in last column of cost matrix + cvSetImageROI(cost, cvRect(cost->width-1, 0, 1, cost->height)); + CvPoint max_loc; + cvMinMaxLoc(cost, 0, 0, 0, &max_loc); + int h = max_loc.y; + int h0 = h; + cvResetImageROI(cost); + + // Store the point in output vector + rOptimalPath[rOptimalPath.size()-1] = h0; + + float h1 , h2 , h3; + + // Backward process + for (int w = rOptimalPath.size() - 2; w >= 0; w--) { + // Constraint to close the contour + if (h - h0 > w) { + h--; + } else if (h0 - h > w) { + h++; + } else { + // When no need to constraint : use the cost matrix + // h1 is the value above line h + h1 = (h == 0) ? 0 : (reinterpret_cast(cost->imageData + + (h - 1) * cost->widthStep))[w]; + + // h2 is the value at line h + h2 = (reinterpret_cast(cost->imageData + + h * cost->widthStep))[w]; + + // h3 is the value below line h + h3 = (h == cost->height - 1) ? 0 : + (reinterpret_cast(cost->imageData + + (h + 1) * cost->widthStep))[w]; + + // h1 is the maximum => h decreases + if (h1 > h2 && h1 > h3) + h--; + + // h3 is the maximum => h increases + else if (h3 > h2 && h3 > h1) + h++; + } + + // Store the point in output contour + rOptimalPath[w] = h; + } + + // Release memory + cvReleaseImage(&cost); + } // end of function + + // Find a contour in image using Viterbi algorithm and anisotropic smoothing + vector EOC2Processings::findContour(const IplImage *pSrc, + const CvPoint &rCenter, + const vector &rTheta, + int minRadius, + int maxRadius, + const IplImage *pMask) { + // Output + vector contour; + contour.resize(rTheta.size()); + + // Unwrap the image + IplImage * unwrapped = unwrapRing(pSrc, rCenter, minRadius, maxRadius, + rTheta); + + // Smooth image + processAnisotropicSmoothing(unwrapped, unwrapped, 100, 1); + + // Extract the gradients + computeVerticalGradients(unwrapped, unwrapped); + + // Take into account the mask + if (pMask) { + IplImage * mask_unwrapped = unwrapRing(pMask, rCenter, minRadius, + maxRadius, rTheta); + IplImage * temp = cvCloneImage(unwrapped); + cvZero(unwrapped); + cvCopy(temp, unwrapped, mask_unwrapped); + cvReleaseImage(&temp); + cvReleaseImage(&mask_unwrapped); + } + + // Find optimal path in unwrapped image + vector optimalPath; + runViterbi(unwrapped, optimalPath); + for (int i = 0; i < static_cast(optimalPath.size()); i++) { + contour[i] = convertPolarToCartesian(rCenter, + minRadius+optimalPath[i], + rTheta[i]); + } + + // Release memory + cvReleaseImage(&unwrapped); + + return contour; + } // end of function + + // Draw a contour (vector of CvPoint) on an image + void EOC2Processings::drawContour(IplImage * pImage, + const vector &rContour, + const CvScalar &rColor, + int thickness) { + // Draw INSIDE the contour if thickness is negative + if (thickness < 0) { + CvPoint * points = new CvPoint[rContour.size()]; + for (int i = 0; i < static_cast(rContour.size()); i++) { + points[i].x = rContour[i].x; + points[i].y = rContour[i].y; + } + cvFillConvexPoly(pImage, points, rContour.size(), rColor); + delete [] points; + // Else draw the contour + } else { + // Draw the contour on binary mask + IplImage * mask = cvCreateImage(cvGetSize(pImage), IPL_DEPTH_8U, 1); + cvZero(mask); + for (int i = 0; i < static_cast(rContour.size()); i++) { + // Do not exceed image sizes + int x = min(max(0, rContour[i].x), pImage->width); + int y = min(max(0, rContour[i].y), pImage->height); + + // Plot the point on image + ((uchar *)(mask->imageData + y*mask->widthStep))[x] = 255; + } + + // Dilate mask if user specified thickness + if (thickness > 1) { + IplConvKernel * se = cvCreateStructuringElementEx(3, 3, 1, 1, + CV_SHAPE_ELLIPSE); + cvDilate(mask, mask, se, thickness-1); + cvReleaseStructuringElement(&se); + } + + // Color rgb + cvSet(pImage, rColor, mask); + + // Release memory + cvReleaseImage(&mask); + } + } // end of function +} // end of namespace + + diff --git a/acii/acii-client/src/EOC2Utils.cpp b/acii/acii-client/src/EOC2Utils.cpp new file mode 100755 index 0000000..edae8da --- /dev/null +++ b/acii/acii-client/src/EOC2Utils.cpp @@ -0,0 +1,147 @@ +/***************************************************************************** + * Eye Orientation CLassifier v2 (U/D + L/R) + * Author: Adam Czajka, aczajka@nd.edu, May 2016 + * + * Code in this file is partially based on Open Source for Iris (OSIRIS) v. 4.1 + * under BSD license http://opensource.org/licenses/bsd-license.php + ******************************************************************************/ + + +#include +#include +#include "EOC2Utils.h" + +using std::runtime_error; +using std::vector; + +namespace EOC2 { + // Default constructor + EOC2Circle::EOC2Circle() { + // Do nothing + } + + // Copying constructor + EOC2Circle::EOC2Circle(const CvPoint &rCenter, int rRadius) { + setCenter(rCenter); + setRadius(rRadius); + } + + // Default constructor + EOC2Circle::~EOC2Circle() { + // Do nothing + } + + CvPoint EOC2Circle::getCenter() const { + return mCenter; + } + + CvPoint2D32f EOC2Circle::getCenterFloat() const { + return fCenter; + } + + + int EOC2Circle::getRadius() const { + return mRadius; + } + + float EOC2Circle::getRadiusFloat() const { + return fRadius; + } + + + void EOC2Circle::setCenter(const CvPoint & rCenter) { + mCenter = rCenter; + } + + void EOC2Circle::setCenter(const CvPoint2D32f & rCenter) { + fCenter = rCenter; + } + + void EOC2Circle::setRadius(int rRadius) { + if (rRadius < 0) { + throw runtime_error("Circle with negative radius : " + rRadius); + } + mRadius = rRadius; + } + + void EOC2Circle::setRadius(float rRadius) { + if (rRadius < 0) { + throw runtime_error("Circle with negative radius : " + + static_cast(rRadius)); + } + fRadius = rRadius; + } + + + void EOC2Circle::setCircle(const CvPoint & rCenter, int rRadius) { + setCenter(rCenter); + setRadius(rRadius); + } + + void EOC2Circle::setCircle(const CvPoint2D32f & rCenter, float rRadius) { + setCenter(rCenter); + setRadius(rRadius); + } + + void EOC2Circle::setCircle(int rCenterX, int rCenterY, int rRadius) { + setCircle(cvPoint(rCenterX, rCenterY), rRadius); + } + + void EOC2Circle::setCircle(float rCenterX, float rCenterY, float rRadius) { + setCircle(cvPoint2D32f(rCenterX, rCenterY), rRadius); + } + + void EOC2Circle::drawCircle(IplImage *pImage, const CvScalar &rColor, + int thickness ) { + cvCircle(pImage, mCenter, mRadius, rColor, thickness); + } + + + + void EOC2Circle::computeCircleFittingFloat(const vector &rPoints) { + // Compute the averages mx and my + float mx = 0, my = 0; + + for (int p = 0; p < static_cast(rPoints.size()); p++) { + mx += static_cast(rPoints[p].x); + my += static_cast(rPoints[p].y); + } + + mx = mx / static_cast(rPoints.size()); + my = my / static_cast(rPoints.size()); + + // Work in (u,v) space, with u = x-mx and v = y-my + float u = 0, v = 0, suu = 0, svv = 0, suv = 0, + suuu = 0, svvv = 0, suuv = 0, suvv = 0; + + // Build some sums + for (int p = 0; p < static_cast(rPoints.size()); p++) { + u = static_cast(rPoints[p].x) - mx; + v = static_cast(rPoints[p].y) - my; + suu += u * u; + svv += v * v; + suv += u * v; + suuu += u * u * u; + svvv += v * v * v; + suuv += u * u * v; + suvv += u * v * v; + } + + // These equations are demonstrated in paper from R.Bullock (2006) + float uc = static_cast(0.5 * + (suv * (svvv + suuv) - svv * (suuu + suvv)) / (suv * suv - suu * svv)); + float vc = static_cast(0.5 * + (suv * (suuu + suvv) - suu * (svvv + suuv)) / (suv * suv - suu * svv)); + + // Circle parameters (float) + setCenter(cvPoint2D32f(uc + mx, vc + my)); + setRadius(static_cast(sqrt(uc * uc + vc * vc + + (suu + svv) / static_cast(rPoints.size())))); + + // Circle parameters (int) + setCenter(cvPoint(static_cast(uc + mx), static_cast(vc + my))); + setRadius(static_cast(sqrt(uc * uc + vc * vc + + (suu + svv) / static_cast(rPoints.size())))); + } +} // namespace EOC2 + diff --git a/acii/acii-client/src/configparser.cpp b/acii/acii-client/src/configparser.cpp new file mode 100755 index 0000000..2e116d8 --- /dev/null +++ b/acii/acii-client/src/configparser.cpp @@ -0,0 +1,175 @@ +// ####################################################################### +// NOTICE +// +// This software (or technical data) was produced for the U. S. Government +// and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +// (May 2014) – Alternative IV (Dec 2007) +// +// (c) 2024 The MITRE Corporation. All Rights Reserved. +// ####################################################################### + +#include +#include +#include +#include +#include +#include +#include +#include +#include "configparser.h" + +using std::getline; +using std::ifstream; +using std::isspace; +using std::locale; +using std::map; +using std::string; +using std::stringstream; +using std::vector; + +namespace { +string expandEnvVars(const string &value) +{ + string expanded; + string::size_type pos = 0; + + while (pos < value.length()) + { + string::size_type start = value.find("${", pos); + if (start == string::npos) + { + expanded.append(value.substr(pos)); + break; + } + + expanded.append(value.substr(pos, start - pos)); + string::size_type end = value.find("}", start + 2); + if (end == string::npos) + { + expanded.append(value.substr(start)); + break; + } + + string env_var = value.substr(start + 2, end - start - 2); + const char *env_value = std::getenv(env_var.c_str()); + if (env_value != nullptr) + { + expanded.append(env_value); + } + else + { + expanded.append(value.substr(start, end - start + 1)); + } + + pos = end + 1; + } + + return expanded; +} +} + +ConfigParser::ConfigParser() {} + +// Remove whitespace from front and back of given string +void ConfigParser::strip(string &s) +{ + std::locale loc; + + // Do nothing if the string is empty: + if (s.empty()) { + return; + } + + int front = 0; + int end = s.length() - 1; + while (front < s.length() && isspace(s[front], loc)) + { + front++; + } + + // Do nothing if the string is just empty white space: + if (front == s.length()) { + s.clear(); + return; + } + + while (end > front && isspace(s[end], loc)) + { + end--; + } + + s = s.substr(front, end - front + 1); +} + +// Returns true on sucess, false if config cannot be opened +bool ConfigParser::parseParams(string &config_file, map ¶ms) +{ + ifstream file; + string line; + string prop; + string value; + string::size_type eqIndex; + string::size_type commentIndex; + file.open(config_file.c_str()); + if (!file.is_open()) + { + return false; + } + while (getline(file, line)) + { + if (line.empty()) + { + continue; + } + // Remove all commented out lines: + // removeto the right of '#' (treated as comment) to make an empty string + commentIndex = line.find("#"); + if (commentIndex != string::npos) { + line = line.substr(0, commentIndex); + strip(line); + } + // ignore the line if its empty + if (line.empty()) { + continue; + } + + // Use '=' as delimiter separating prop and value + eqIndex = line.find("="); + if (eqIndex == string::npos) { //If the line doesn't have a '=', ignore the line + continue; + } + + prop = line.substr(0, eqIndex); + value = line.substr(eqIndex + 1, line.length()); + strip(prop); + strip(value); + value = expandEnvVars(value); + + if (prop.empty()) { // ignore the ine if the property is empty + continue; + } + + params[prop] = value; + } + return true; +} + +void ConfigParser::parseDelimitedString(vector &delimited, + string &s, char delim) +{ + // Remove the line if it's empty: + if (s.empty()) { + return; + } + + stringstream ss(s); + string substr; + while (getline(ss, substr, delim)) + { + strip(substr); + if (!substr.empty()) { + delimited.push_back(substr); + } + } +} diff --git a/acii/acii-client/trainedClassifiers/FC_LR_ALL.xml b/acii/acii-client/trainedClassifiers/FC_LR_ALL.xml new file mode 100755 index 0000000..7a2442f --- /dev/null +++ b/acii/acii-client/trainedClassifiers/FC_LR_ALL.xml @@ -0,0 +1,15 @@ + + + + 1 + 2 +
d
+ + 8.7955310206578297e+000 -1.9150617063961139e-001
+ + 1 + 2 +
d
+ + 8.7563810831555031e+001 5.3410901704925520e+000
+
diff --git a/acii/acii-client/trainedClassifiers/FC_LR_N00006.xml b/acii/acii-client/trainedClassifiers/FC_LR_N00006.xml new file mode 100755 index 0000000..8b01f7e --- /dev/null +++ b/acii/acii-client/trainedClassifiers/FC_LR_N00006.xml @@ -0,0 +1,15 @@ + + + + 1 + 2 +
d
+ + 5.4371719730470964e+000 -2.0686098520690999e-001
+ + 1 + 2 +
d
+ + 8.3767336936838177e+001 4.7427920391951330e+000
+
diff --git a/acii/acii-client/trainedClassifiers/FC_LR_N00020.xml b/acii/acii-client/trainedClassifiers/FC_LR_N00020.xml new file mode 100755 index 0000000..31babe8 --- /dev/null +++ b/acii/acii-client/trainedClassifiers/FC_LR_N00020.xml @@ -0,0 +1,15 @@ + + + + 1 + 2 +
d
+ + 1.8813153858209297e+001 -1.6201999649615892e-001
+ + 1 + 2 +
d
+ + 6.7699457852432886e+001 5.5538024135707031e+000
+
diff --git a/acii/acii-client/trainedClassifiers/FC_LR_N00049.xml b/acii/acii-client/trainedClassifiers/FC_LR_N00049.xml new file mode 100755 index 0000000..ba5c7ae --- /dev/null +++ b/acii/acii-client/trainedClassifiers/FC_LR_N00049.xml @@ -0,0 +1,15 @@ + + + + 1 + 2 +
d
+ + 2.0357500468295044e+000 -1.8927170455090109e-001
+ + 1 + 2 +
d
+ + 1.1784626660800059e+002 5.5551734147385172e+000
+
diff --git a/acii/acii-client/trainedClassifiers/FC_LR_N00074.xml b/acii/acii-client/trainedClassifiers/FC_LR_N00074.xml new file mode 100755 index 0000000..2f2cd3a --- /dev/null +++ b/acii/acii-client/trainedClassifiers/FC_LR_N00074.xml @@ -0,0 +1,15 @@ + + + + 1 + 2 +
d
+ + 7.1868085046509407e+000 -2.1674703473421800e-001
+ + 1 + 2 +
d
+ + 7.1541743948956480e+001 5.5868695916059572e+000
+
diff --git a/acii/acii-client/trainedClassifiers/FC_UD_ALL.xml b/acii/acii-client/trainedClassifiers/FC_UD_ALL.xml new file mode 100755 index 0000000..d652eed --- /dev/null +++ b/acii/acii-client/trainedClassifiers/FC_UD_ALL.xml @@ -0,0 +1,19 @@ + + + + 1 + 6 +
d
+ + 6.4270928130524291e-002 9.1067103608461142e-001 + 3.2301525560328631e-001 1.5299557654399942e+000 + -1.2940165357530544e-004 9.8637856158220521e-004
+ + 1 + 6 +
d
+ + 4.8884811834448294e-001 2.4170308459828224e+001 + 5.7892124523687096e+000 1.0943552959361664e+002 + 5.7650644733559499e-002 8.6974004990004641e-002
+
diff --git a/acii/acii-client/trainedClassifiers/FC_UD_N00006.xml b/acii/acii-client/trainedClassifiers/FC_UD_N00006.xml new file mode 100755 index 0000000..b7cf41a --- /dev/null +++ b/acii/acii-client/trainedClassifiers/FC_UD_N00006.xml @@ -0,0 +1,19 @@ + + + + 1 + 6 +
d
+ + 8.3307243795955874e-002 9.2384114982255916e-001 + 3.5102366777806498e-001 -3.6399121312150617e-001 + -4.0045274940596939e-004 7.1493475608493551e-004
+ + 1 + 6 +
d
+ + 5.6677461824379682e-001 3.4094152778052916e+001 + 5.2902824852458323e+000 7.1130472542423973e+001 + 5.8051664124900544e-002 8.6306607332794064e-002
+
diff --git a/acii/acii-client/trainedClassifiers/FC_UD_N00020.xml b/acii/acii-client/trainedClassifiers/FC_UD_N00020.xml new file mode 100755 index 0000000..6927817 --- /dev/null +++ b/acii/acii-client/trainedClassifiers/FC_UD_N00020.xml @@ -0,0 +1,19 @@ + + + + 1 + 6 +
d
+ + 5.4082095077614377e-002 8.8656564226337498e-001 + 2.7233232984355848e-001 5.9547483461744637e+000 + 8.9474571718502584e-004 1.4549088242044187e-003
+ + 1 + 6 +
d
+ + 4.8883568367603941e-001 2.0212461439013794e+001 + 5.7901516590639250e+000 1.5585490316076917e+002 + 6.1712769772495964e-002 9.9103404695925157e-002
+
diff --git a/acii/acii-client/trainedClassifiers/FC_UD_N00049.xml b/acii/acii-client/trainedClassifiers/FC_UD_N00049.xml new file mode 100755 index 0000000..cf30144 --- /dev/null +++ b/acii/acii-client/trainedClassifiers/FC_UD_N00049.xml @@ -0,0 +1,19 @@ + + + + 1 + 6 +
d
+ + 4.2048527644230768e-002 1.1128113775986892e+000 + 3.6868818429800182e-001 1.5353602852529058e-001 + -1.1439742030107971e-003 1.3375732047699366e-004
+ + 1 + 6 +
d
+ + 4.4051718830265130e-001 1.8666695600724708e+001 + 6.4089779815186541e+000 5.0890733116610448e+001 + 5.1646280219790397e-002 7.1460982365463999e-002
+
diff --git a/acii/acii-client/trainedClassifiers/FC_UD_N00074.xml b/acii/acii-client/trainedClassifiers/FC_UD_N00074.xml new file mode 100755 index 0000000..48047c6 --- /dev/null +++ b/acii/acii-client/trainedClassifiers/FC_UD_N00074.xml @@ -0,0 +1,19 @@ + + + + 1 + 6 +
d
+ + 7.9411433293269232e-002 6.6801673091610037e-001 + 2.9867324227176945e-001 -5.2413507611378274e-001 + 3.5003685304130484e-005 1.7821037430768899e-003
+ + 1 + 6 +
d
+ + 4.1338777079535344e-001 1.6701852333074715e+001 + 5.6691107009747457e+000 1.2579054646030009e+002 + 5.7968063765260293e-002 8.6501192145131134e-002
+
diff --git a/acii/acii-client/trainedClassifiers/SVM_LR_ALL.xml b/acii/acii-client/trainedClassifiers/SVM_LR_ALL.xml new file mode 100755 index 0000000..1faaa3a --- /dev/null +++ b/acii/acii-client/trainedClassifiers/SVM_LR_ALL.xml @@ -0,0 +1,377 @@ + + + + C_SVC + RBF + 5.0000000000000000e-001 + 1. + 2.2204460492503131e-016 + 100000 + 2 + 2 + 2 + + 1 + 2 +
i
+ + -1 1
+ 162 + + <_> + -4.31674458e-002 1.04522929e-001 + <_> + -3.99340123e-001 -1.29008874e-001 + <_> + 5.15566885e-001 4.46155757e-001 + <_> + 6.68473005e-001 3.53876084e-001 + <_> + 2.01278299e-001 2.35721737e-001 + <_> + 1.64090902e-001 3.06217790e-001 + <_> + 3.55848610e-001 5.18980205e-001 + <_> + -2.52609104e-001 1.02320284e-001 + <_> + 6.55712634e-002 1.27889246e-001 + <_> + 3.48098189e-001 3.00646901e-001 + <_> + -1.01278320e-001 -6.77401781e-001 + <_> + 1.98014174e-002 -8.53464842e-001 + <_> + -9.12294805e-001 -6.80287242e-001 + <_> + -5.25052547e-001 -1.71244815e-001 + <_> + -3.42921287e-001 -3.94974872e-002 + <_> + -2.02997193e-001 -1.09987848e-001 + <_> + 2.89219886e-001 4.07088101e-001 + <_> + 3.11259121e-001 7.29051411e-001 + <_> + -1.36651695e-001 -4.38830495e-001 + <_> + 4.29112107e-001 5.27030826e-001 + <_> + 2.03975484e-001 3.01943928e-001 + <_> + 3.86798948e-001 8.64038467e-001 + <_> + 3.53412837e-001 4.75110114e-001 + <_> + -2.98551004e-002 2.66084522e-001 + <_> + 9.65788364e-002 -1.10602248e-002 + <_> + -6.26404941e-001 -3.18859190e-001 + <_> + 1.80530325e-001 1.51267886e+000 + <_> + -3.13885897e-001 -5.69872335e-002 + <_> + -4.04657349e-002 2.43269578e-001 + <_> + -1.60409123e-001 -3.26298445e-001 + <_> + -4.45076749e-002 1.61334604e-001 + <_> + 4.46650535e-002 -1.56223401e-001 + <_> + 1.19261004e-001 2.29350910e-001 + <_> + 4.53023165e-002 2.33390525e-001 + <_> + 3.73494357e-001 4.26920474e-001 + <_> + 3.60235333e-001 4.14927334e-001 + <_> + 2.28955865e-001 3.99334550e-001 + <_> + 3.74862641e-001 8.94709766e-001 + <_> + 2.55041242e-001 6.45190895e-001 + <_> + -2.60794222e-001 -9.19895768e-002 + <_> + -5.07743537e-001 -1.83369353e-001 + <_> + -4.06692266e-001 -1.57760397e-001 + <_> + -4.36607867e-001 -3.18190664e-001 + <_> + 1.74391523e-001 5.05895734e-001 + <_> + -1.73249066e-001 2.45706309e-002 + <_> + -2.05155108e-002 1.92091644e-001 + <_> + 2.93366730e-001 6.22478783e-001 + <_> + 1.66260421e-001 5.46800375e-001 + <_> + 3.10983092e-001 6.37774467e-001 + <_> + 4.86585885e-001 7.79463708e-001 + <_> + 1.50619805e-001 3.10674518e-001 + <_> + 2.60642618e-001 7.50540793e-001 + <_> + 1.29897773e-001 4.52569425e-001 + <_> + 3.48084956e-001 7.95633614e-001 + <_> + 2.32534096e-001 6.84204280e-001 + <_> + -3.03699672e-001 -1.21558167e-001 + <_> + -1.11797380e+000 -1.04032683e+000 + <_> + 8.32976867e-003 1.95713890e+000 + <_> + -2.42475748e-001 3.09816027e+000 + <_> + -8.37159574e-001 -6.94925845e-001 + <_> + 2.63449937e-001 1.52907729e+000 + <_> + 3.13132405e-001 1.65746498e+000 + <_> + 3.14875692e-001 1.29885948e+000 + <_> + 2.92675078e-001 1.42718995e+000 + <_> + -1.85796767e-001 1.06956981e-001 + <_> + 5.89515083e-002 3.67651880e-001 + <_> + -3.29088092e-001 4.54848957e+000 + <_> + -2.19240475e+000 2.89395165e+000 + <_> + -9.41305816e-001 -1.31597459e+000 + <_> + -7.72759557e-001 -2.82783484e+000 + <_> + -7.05225706e-001 -1.16854882e+000 + <_> + -2.17172790e+000 1.53538525e+000 + <_> + -2.35543203e+000 1.25128686e+000 + <_> + -1.23323083e+000 -1.53181672e+000 + <_> + -2.14543104e+000 3.96187162e+000 + <_> + -5.41551709e-001 -3.01775098e-001 + <_> + -6.14760339e-001 -2.55259573e-001 + <_> + -6.16099954e-001 -4.42847252e-001 + <_> + -4.65036482e-001 -2.37027049e-001 + <_> + -1.65444344e-001 1.00748837e-002 + <_> + -4.93065268e-001 4.35126257e+000 + <_> + -1.25438467e-001 3.39133215e+000 + <_> + -1.43430024e-001 3.22867632e-001 + <_> + 6.28286541e-001 1.41211140e+000 + <_> + 8.62181902e-001 1.29669392e+000 + <_> + 2.64614850e-001 4.29223120e-001 + <_> + -4.93138373e-001 -3.27995449e-001 + <_> + -3.73395562e-001 -3.94743294e-001 + <_> + -4.11989480e-001 -7.10887313e-002 + <_> + 1.45885527e-001 8.05423856e-002 + <_> + 3.05263679e-002 -1.10153548e-001 + <_> + -7.59830475e-002 -9.83375460e-002 + <_> + 3.25683326e-001 1.82909667e-001 + <_> + -2.11547211e-001 -2.68829703e-001 + <_> + 2.44556934e-001 1.17098860e-001 + <_> + 2.32013062e-001 8.90264511e-001 + <_> + 3.92346159e-002 -2.20245808e-001 + <_> + -1.02496214e-001 4.17841345e-001 + <_> + 4.14434880e-001 2.88430929e-001 + <_> + -2.78573185e-001 -5.71143448e-001 + <_> + 5.05719900e-001 8.35012674e-001 + <_> + 6.93865657e-001 5.70598066e-001 + <_> + 6.73841000e-001 5.67884028e-001 + <_> + -6.65883869e-002 1.94897085e-001 + <_> + -2.18617424e-001 1.14298868e+000 + <_> + -1.09543599e-001 1.76013187e-001 + <_> + 4.85329986e-001 7.48489499e-001 + <_> + -7.18104094e-002 4.10139233e-001 + <_> + -2.38516420e-001 5.38098335e-001 + <_> + -1.59026906e-001 2.34944656e-001 + <_> + -1.51555553e-001 8.92829955e-001 + <_> + 8.05480838e-001 1.73604596e+000 + <_> + -1.20750897e-001 1.01667523e+000 + <_> + -1.41085193e-001 6.18164897e-001 + <_> + -3.67616594e-001 6.60664635e-003 + <_> + -4.53329414e-001 -8.14434290e-001 + <_> + -4.31252182e-001 -6.57666564e-001 + <_> + -4.16655898e-001 -6.67254210e-001 + <_> + 3.72337550e-001 4.38422233e-001 + <_> + 5.77322066e-001 2.80665964e-001 + <_> + -1.31689146e-001 -7.64539316e-002 + <_> + -7.83761442e-002 3.38497385e-002 + <_> + 3.77466589e-001 -3.61522683e-003 + <_> + -5.85852563e-001 -8.30409884e-001 + <_> + -5.17929018e-001 -5.79925478e-001 + <_> + -1.86561242e-001 -4.98773277e-001 + <_> + -3.53504241e-001 -6.08676970e-001 + <_> + 1.77355313e+000 -2.22257876e+000 + <_> + 8.65545034e-001 4.05799961e+000 + <_> + -4.85022098e-001 -1.19998002e+000 + <_> + 5.33639431e-001 1.43484890e-001 + <_> + -2.81224340e-001 -4.24757540e-001 + <_> + -4.46202397e-001 -8.98637593e-001 + <_> + 7.32405663e-001 5.02553165e-001 + <_> + 8.35309744e-001 1.80677056e+000 + <_> + 9.65249017e-002 -2.08744064e-001 + <_> + 8.86421919e-001 6.47619247e-001 + <_> + 2.29698730e+000 -8.80502224e-001 + <_> + 3.55548084e-001 7.96910375e-002 + <_> + 8.25201690e-001 -2.33612204e+000 + <_> + 5.29237390e-001 2.14489490e-001 + <_> + -4.81202379e-002 1.78401530e-001 + <_> + 7.75784314e-001 1.43737757e+000 + <_> + -9.75493491e-002 6.95763171e-001 + <_> + 5.73300540e-001 2.89105177e-001 + <_> + 5.98354498e-003 2.49337569e-001 + <_> + -7.63654476e-003 2.46115014e-001 + <_> + 5.68364978e-001 7.02305377e-001 + <_> + 7.06027746e-001 1.19074988e+000 + <_> + -1.34608056e-002 6.42739713e-001 + <_> + 4.59163606e-001 1.01386093e-001 + <_> + -8.59738439e-002 2.65958816e-001 + <_> + 5.16575463e-002 2.16203615e-001 + <_> + -3.27012330e-001 -4.97704834e-001 + <_> + -5.68708062e-001 -1.32325387e+000 + <_> + -3.29207987e-001 -5.65286875e-001 + <_> + 7.93168783e-001 3.41263866e+000 + <_> + 2.71622777e-001 1.33659961e-002 + <_> + 1.34144127e-001 4.45115678e-002 + <_> + 2.29728013e-001 6.97262883e-002 + <_> + 5.03654540e-001 4.23543358e+000 + <_> + -8.61669183e-002 1.68710780e+000 + + <_> + 162 + -9.9845265619269041e-002 + + 1. 1. 1. 1. 1. 1. 1. 9.3656421787487043e-001 1. 1. 1. 1. 1. 1. + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 8.7768755142154042e-001 1. + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1. 1. 1. 1. 1. 1. 1. 8.1269032303234034e-001 1. + 3.3888494848093859e-001 1. 1. 1. 1. 1. 1. 1. 1. + 2.4627462531627908e-001 1. 1. 1. 3.0453585176148085e-001 + 3.4645696250390950e-001 4.7110587786544617e-001 + 4.4689983529980559e-001 1. 1.7659402850896774e-001 1. 1. 1. + 6.9421421948248782e-002 1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -3.5741045142319960e-001 -1. -1. -1. -1. + -7.0431565717639477e-001 -3.8464935344913936e-001 -1. -1. -1. + -1. -1. -1. -1. -2.5174658484283230e-001 + -1.8615875520757649e-001 -1. -1.4283484191468482e-001 -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1. + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 + 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 + 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 + 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 + 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 + 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 + 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 + 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 + 155 156 157 158 159 160 161
+
diff --git a/acii/acii-client/trainedClassifiers/SVM_LR_N00006.xml b/acii/acii-client/trainedClassifiers/SVM_LR_N00006.xml new file mode 100755 index 0000000..2fdf197 --- /dev/null +++ b/acii/acii-client/trainedClassifiers/SVM_LR_N00006.xml @@ -0,0 +1,164 @@ + + + + C_SVC + RBF + 5.0000000000000000e-001 + 1. + 2.2204460492503131e-016 + 100000 + 2 + 2 + 2 + + 1 + 2 +
i
+ + -1 1
+ 63 + + <_> + -5.03235450e-003 1.20945901e-001 + <_> + -3.77347350e-001 -1.42045692e-001 + <_> + 5.79024792e-001 5.05675316e-001 + <_> + 7.38860846e-001 4.01754677e-001 + <_> + 2.50492096e-001 2.68695295e-001 + <_> + 2.11619288e-001 3.48084360e-001 + <_> + 4.12067801e-001 5.87686479e-001 + <_> + -2.23966241e-001 1.18465394e-001 + <_> + 1.08634576e-001 1.47259861e-001 + <_> + 4.03966129e-001 3.41810703e-001 + <_> + -6.57769069e-002 -7.59617805e-001 + <_> + 6.07903674e-002 -9.57890987e-001 + <_> + -9.13550019e-001 -7.62867212e-001 + <_> + -5.08757293e-001 -1.89609647e-001 + <_> + -3.18371564e-001 -4.12425473e-002 + <_> + -1.72105834e-001 -1.20625183e-001 + <_> + 3.42419356e-001 4.61679339e-001 + <_> + 3.65457445e-001 8.24257970e-001 + <_> + -2.40318990e+000 2.36095884e-003 + <_> + -1.02753460e-001 -4.90950972e-001 + <_> + 4.88651723e-001 5.96752703e-001 + <_> + 2.53311515e-001 3.43271345e-001 + <_> + 4.44420874e-001 9.76273417e-001 + <_> + 4.09521639e-001 5.38282275e-001 + <_> + 8.88333190e-003 3.02888304e-001 + <_> + 1.41047463e-001 -9.21795517e-003 + <_> + -6.14703119e-001 -3.55845392e-001 + <_> + -7.56395459e-001 -3.60703468e-001 + <_> + 2.28803784e-001 1.70673907e+000 + <_> + -2.88020223e-001 -6.09386042e-002 + <_> + -1.42679131e+000 2.68582797e+000 + <_> + -1.96215844e+000 1.51260972e+000 + <_> + 1.39142144e+000 6.38705790e-001 + <_> + -1.09839000e-001 3.66834551e-001 + <_> + 6.96853101e-001 1.59348524e+000 + <_> + 7.31852055e-001 2.40878865e-001 + <_> + 9.41348970e-001 1.46350789e+000 + <_> + 3.16699177e-001 4.86606658e-001 + <_> + -4.75396723e-001 -3.66134197e-001 + <_> + -3.50226939e-001 -4.41302210e-001 + <_> + -3.90570045e-001 -7.68189952e-002 + <_> + 1.92588821e-001 9.39402282e-002 + <_> + 3.98470640e-001 -1.49490431e-001 + <_> + 7.20013902e-002 -1.20811783e-001 + <_> + -3.93352099e-002 -1.07505202e-001 + <_> + 3.80535394e-001 2.09221050e-001 + <_> + 1.03982818e+000 1.00555694e+000 + <_> + -1.81043357e-001 -2.99504757e-001 + <_> + 4.26119477e-001 -9.36902985e-002 + <_> + -2.76351243e-001 -8.92143130e-001 + <_> + 2.95732200e-001 1.35108277e-001 + <_> + 2.82619834e-001 1.00580788e+000 + <_> + 4.11238909e-001 -1.39220938e-001 + <_> + 9.91857290e-001 3.45471948e-001 + <_> + 2.30756164e+000 -1.38298738e+000 + <_> + 8.11043084e-002 -2.44792059e-001 + <_> + 1.21090278e-001 -3.32970679e-001 + <_> + 1.05352163e+000 -2.36028767e+000 + <_> + -6.70500025e-002 4.73789096e-001 + <_> + 4.73309308e-001 3.28053713e-001 + <_> + -3.23907584e-002 -4.73153085e-001 + <_> + -3.42876017e-001 -1.07961273e+000 + <_> + -2.51107067e-001 -6.39955103e-001 + + <_> + 63 + 3.6942614579902035e-002 + + 1. 1. 1. 1. 1. 1. 1. 1.3317443768341461e-001 1. 1. 1. 1. 1. 1. + 1. 1. 1. 3.4784340820951049e-001 3.9301716261412362e-001 1. 1. + 1. 1. 1. 1. 1. 1. 1. 1. 1. 7.9263944367608963e-001 + 2.1087877615866421e-001 -3.2611130553784763e-002 -1. -1. -1. -1. + -1. -1. -1. -1. -1. -8.9233608247472318e-001 -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -1. -5.0815271711943344e-001 -1. -1. + -4.9169241560651067e-001 -1. -1. -1. -9.5276088258735037e-001 + -1. + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 + 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 + 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
+
diff --git a/acii/acii-client/trainedClassifiers/SVM_LR_N00020.xml b/acii/acii-client/trainedClassifiers/SVM_LR_N00020.xml new file mode 100755 index 0000000..71497cd --- /dev/null +++ b/acii/acii-client/trainedClassifiers/SVM_LR_N00020.xml @@ -0,0 +1,201 @@ + + + + C_SVC + RBF + 5.0000000000000000e-001 + 1. + 2.2204460492503131e-016 + 100000 + 2 + 2 + 2 + + 1 + 2 +
i
+ + -1 1
+ 80 + + <_> + -2.00311169e-001 2.28643090e-001 + <_> + -3.55448335e-001 -3.19110334e-001 + <_> + -2.05539092e-001 1.49846256e-001 + <_> + -9.02013257e-002 -1.55549183e-001 + <_> + 6.28255261e-003 2.15257525e-001 + <_> + -8.93770754e-002 2.19142407e-001 + <_> + 3.35113019e-001 4.05260116e-001 + <_> + 3.17963511e-001 3.93726319e-001 + <_> + 1.48164064e-001 3.78730744e-001 + <_> + 3.36882770e-001 8.55132937e-001 + <_> + 1.81903392e-001 6.15170717e-001 + <_> + -1.31869614e+000 -5.24960160e-001 + <_> + -4.85288382e-001 -9.37755406e-002 + <_> + -8.04697514e-001 -1.81655452e-001 + <_> + -8.40901732e-001 -1.70154631e-001 + <_> + -6.73995852e-001 -1.57027319e-001 + <_> + -7.12689221e-001 -3.11313063e-001 + <_> + 7.75894523e-002 4.81210589e-001 + <_> + -3.72055739e-001 1.83203816e-002 + <_> + -1.74507156e-001 1.79425284e-001 + <_> + 2.31474340e-001 5.93328476e-001 + <_> + 2.54259706e-001 6.08038306e-001 + <_> + 4.81387794e-001 7.44300842e-001 + <_> + 4.68426347e-002 2.93466389e-001 + <_> + 2.00403668e-002 4.29926693e-001 + <_> + 3.02247971e-001 7.59851396e-001 + <_> + -1.24903357e+000 -5.16404629e-001 + <_> + -5.40783167e-001 -1.22211643e-001 + <_> + -6.35505080e-001 -1.69019140e-002 + <_> + -1.59398139e+000 -1.00579119e+000 + <_> + -1.71636569e+000 2.61780262e+000 + <_> + -1.92475915e+000 1.43102574e+000 + <_> + -1.04251862e+000 4.43281698e+000 + <_> + -4.61594909e-001 2.97419071e+000 + <_> + -1.23077071e+000 -6.73619151e-001 + <_> + 1.92779362e-001 1.46520388e+000 + <_> + 2.57039666e-001 1.58867431e+000 + <_> + 2.30579764e-001 1.36721897e+000 + <_> + -3.88285190e-001 9.75513086e-002 + <_> + -7.17229396e-002 3.48261535e-001 + <_> + -5.73621035e-001 4.36897182e+000 + <_> + -1.62985444e+000 1.91871428e+000 + <_> + 5.06136060e-001 7.97722220e-001 + <_> + 7.49487519e-001 5.43434799e-001 + <_> + 7.23587275e-001 5.40824711e-001 + <_> + -2.34098732e-001 1.82123289e-001 + <_> + -4.30736065e-001 1.09390271e+000 + <_> + -2.89657831e-001 1.63962647e-001 + <_> + 4.79763389e-001 7.14512944e-001 + <_> + -2.40852997e-001 3.89121592e-001 + <_> + -4.56473827e-001 5.12179852e-001 + <_> + -3.53660524e-001 2.20637023e-001 + <_> + -3.43996912e-001 8.53325129e-001 + <_> + 8.93852830e-001 1.66424561e+000 + <_> + -3.04153591e-001 9.72427011e-001 + <_> + -3.30454350e-001 5.89179814e-001 + <_> + -6.23454571e-001 1.04442239e-003 + <_> + -3.58851492e-001 -4.95672375e-001 + <_> + -7.34317243e-001 -7.88550377e-001 + <_> + -7.05762088e-001 -6.37786925e-001 + <_> + -6.86882973e-001 -6.47007346e-001 + <_> + 3.33616763e-001 4.16321337e-001 + <_> + 5.98747730e-001 2.64607191e-001 + <_> + 5.93949318e-001 1.60687685e-001 + <_> + 4.56793815e-001 4.05087806e-002 + <_> + -3.18301320e-001 -7.88349062e-002 + <_> + -2.49345243e-001 2.72440966e-002 + <_> + 2.60412633e-001 -1.06177524e-001 + <_> + 3.40250760e-001 -8.78595002e-003 + <_> + 6.22856140e-001 1.75040364e-001 + <_> + -9.05725300e-001 -8.03914130e-001 + <_> + -8.17871571e-001 -5.63023329e-001 + <_> + -3.89273971e-001 -4.84979302e-001 + <_> + -4.53373134e-001 -6.01300776e-001 + <_> + -6.05201364e-001 -5.90673685e-001 + <_> + 2.14597654e+000 -2.14276266e+000 + <_> + 1.00417519e+000 1.93131959e+000 + <_> + 9.71540987e-001 3.89726782e+000 + <_> + 5.42247772e-001 1.32680193e-001 + <_> + -5.11713147e-001 -4.13798392e-001 + + <_> + 80 + 1.6243911223221005e-001 + + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 8.5124758051243610e-001 1. 1. + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 3.3699448370303431e-001 1. 4.2472052231645130e-001 + 6.5897662902254872e-001 1.0415112444643186e-001 + 7.0293479838248896e-001 1. 1. 1. 3.9243473976317347e-002 1. + 6.3482926274843476e-001 1. 9.7692619467008024e-002 -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -4.4485334481569966e-001 -1. -1. + -1. -7.0363111972270331e-001 -1. -7.0230603003674730e-001 -1. + -1. -1. -1. + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 + 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 + 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 + 67 68 69 70 71 72 73 74 75 76 77 78 79
+
diff --git a/acii/acii-client/trainedClassifiers/SVM_LR_N00049.xml b/acii/acii-client/trainedClassifiers/SVM_LR_N00049.xml new file mode 100755 index 0000000..d758ce4 --- /dev/null +++ b/acii/acii-client/trainedClassifiers/SVM_LR_N00049.xml @@ -0,0 +1,81 @@ + + + + C_SVC + RBF + 5.0000000000000000e-001 + 1. + 2.2204460492503131e-016 + 100000 + 2 + 2 + 2 + + 1 + 2 +
i
+ + -1 1
+ 23 + + <_> + -1.57167077e+000 2.78202343e+000 + <_> + -3.90438199e-001 1.35729238e-001 + <_> + -3.63924444e-001 4.78718519e-001 + <_> + -4.10898626e-001 2.51923144e-001 + <_> + -6.42061412e-001 -1.26566231e+000 + <_> + -5.16825795e-001 -2.71925902e+000 + <_> + -4.66645837e-001 -1.12391794e+000 + <_> + -3.39203924e-001 5.56946576e-001 + <_> + -5.73311627e-001 -5.00889897e-001 + <_> + -1.41693664e+000 3.93109107e+000 + <_> + -3.10840517e-001 2.14401984e+000 + <_> + -3.85335743e-001 1.45224011e+000 + <_> + 7.82968223e-001 2.13217592e+000 + <_> + 6.78024769e-001 1.73673964e+000 + <_> + 1.44395843e-001 -4.61814314e-001 + <_> + 1.29082322e-001 -2.01101795e-001 + <_> + 2.83738434e-001 -1.45886198e-001 + <_> + 1.76410127e+000 -8.46971989e-001 + <_> + 1.74388230e+000 1.00243437e+000 + <_> + 3.21545392e-001 7.62177035e-002 + <_> + 6.70514107e-001 -2.24649572e+000 + <_> + 6.49263740e-001 -2.07950306e+000 + <_> + 2.28500590e-001 -9.92863953e-001 + + <_> + 23 + -1.9292547967161627e-001 + + 9.9372019351180799e-002 1. 1.6365664930857018e-002 + 6.1856782067695504e-001 3.3938605425834550e-001 1. 1. 1. + 8.2354645337331778e-001 6.6578968323245502e-001 1. + 1.4802540360898495e-001 -8.9979902856135918e-001 -1. -1. -1. + -4.9860130676190961e-001 -1.0617733504922980e-001 + -1.8219211449696943e-001 -1. -1. -3.4635913271503049e-001 + -6.7792418184759595e-001 + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
+
diff --git a/acii/acii-client/trainedClassifiers/SVM_LR_N00074.xml b/acii/acii-client/trainedClassifiers/SVM_LR_N00074.xml new file mode 100755 index 0000000..11c73ed --- /dev/null +++ b/acii/acii-client/trainedClassifiers/SVM_LR_N00074.xml @@ -0,0 +1,121 @@ + + + + C_SVC + RBF + 5.0000000000000000e-001 + 1. + 2.2204460492503131e-016 + 100000 + 2 + 2 + 2 + + 1 + 2 +
i
+ + -1 1
+ 43 + + <_> + -2.54708022e-001 2.33037859e-001 + <_> + -3.28663379e-001 9.24144685e-002 + <_> + 2.13184312e-001 6.94078684e-001 + <_> + -3.64631601e-002 4.50363904e-001 + <_> + -1.59308389e-002 3.22211117e-001 + <_> + 3.20367739e-002 3.94024968e-001 + <_> + 1.18530072e-001 6.14683092e-001 + <_> + -6.40348017e-001 -2.83981413e-001 + <_> + -7.29951978e-001 -2.39512220e-001 + <_> + -7.31591642e-001 -4.18847442e-001 + <_> + -5.46696842e-001 -2.22081780e-001 + <_> + -3.27604830e-001 1.44886047e-001 + <_> + 2.56380048e-002 4.92058218e-001 + <_> + -1.80009812e-001 1.41495550e-002 + <_> + -1.84675848e+000 1.54591978e+000 + <_> + -1.48522878e+000 2.49430275e+000 + <_> + -5.81002831e-001 4.16435862e+000 + <_> + 1.35934994e-001 1.25556707e+000 + <_> + 6.25885874e-002 1.12526214e+000 + <_> + 7.69763738e-002 1.08544695e+000 + <_> + -1.31044447e-001 3.24665761e+000 + <_> + -3.64104770e-002 1.75071135e-001 + <_> + 9.72011030e-001 1.37866175e+000 + <_> + -9.69094411e-002 6.69672787e-001 + <_> + 2.98100747e-002 2.42886513e-001 + <_> + 1.31397098e-002 2.39805743e-001 + <_> + 7.18139172e-001 6.75927222e-001 + <_> + 8.86632085e-001 1.14288378e+000 + <_> + 6.01108139e-003 6.18981957e-001 + <_> + -8.27415511e-002 2.58776575e-001 + <_> + 8.57129619e-002 2.11210206e-001 + <_> + -3.77761602e-001 -4.71291721e-001 + <_> + -6.73586130e-001 -1.26052308e+000 + <_> + -3.80448997e-001 -5.35900712e-001 + <_> + -4.08901870e-001 -8.65396678e-001 + <_> + -3.83031666e-001 -7.47644246e-001 + <_> + 9.93288755e-001 3.26702666e+000 + <_> + 3.54940295e-001 1.72958821e-002 + <_> + 1.86672747e-001 4.70712930e-002 + <_> + 3.03663045e-001 7.11767524e-002 + <_> + 1.94706941e+000 -1.93196905e+000 + <_> + 6.38936579e-001 4.05362511e+000 + <_> + -8.29778686e-002 1.61740589e+000 + + <_> + 43 + 2.5006102894494120e-001 + + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 3.1013983337128875e-001 1. 1. + 3.1525172826116288e-001 5.1006531903433316e-001 1. 1. + 9.5301191016952436e-001 1. 1. -1. -6.5491964698451255e-001 -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1.2439491900996047e-001 -1. -1. + -1. -6.5278818829688712e-001 -7.1550588825724126e-002 -1. -1. + -5.8481544771922589e-001 -1. -1. + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 + 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
+
diff --git a/acii/acii-client/trainedClassifiers/SVM_UD_ALL.xml b/acii/acii-client/trainedClassifiers/SVM_UD_ALL.xml new file mode 100755 index 0000000..24e4143 --- /dev/null +++ b/acii/acii-client/trainedClassifiers/SVM_UD_ALL.xml @@ -0,0 +1,5191 @@ + + + + C_SVC + RBF + 5.0000000000000000e-001 + 1. + 2.2204460492503131e-016 + 100000 + 6 + 6 + 2 + + 1 + 2 +
i
+ + -1 1
+ 1568 + + <_> + -1.99894801e-001 -7.96216652e-002 -2.93306768e-001 + -2.63653457e-001 -4.12376493e-001 3.52491170e-001 + <_> + -8.61127079e-001 -1.98907644e-001 2.61007905e-001 -1.30426168e-001 + -4.66486335e-001 3.37460667e-001 + <_> + -1.89542639e+000 3.37418377e-001 3.21721911e-001 -2.06063971e-001 + 5.91886759e-001 -3.95563155e-001 + <_> + -1.54832935e+000 -7.46804550e-002 -9.71516490e-001 2.39423454e-001 + 1.85762405e-001 -2.10044637e-001 + <_> + -1.82800460e+000 -6.92799762e-002 -4.91069583e-003 3.10710669e-001 + 5.98899484e-001 -3.05638880e-001 + <_> + -1.81601858e+000 3.13713580e-001 8.25296223e-001 6.53169155e-002 + 7.57004559e-001 -6.20576382e-001 + <_> + 4.16889161e-001 2.86705792e-001 -5.20849168e-001 -7.05468953e-001 + 2.12157011e+000 -1.52399004e+000 + <_> + 1.13599505e-002 -1.00660153e-001 -5.41871727e-001 -6.38053954e-001 + 6.86612368e-001 -7.28912830e-001 + <_> + 2.88038731e-001 1.20240666e-001 -7.25497663e-001 -3.92894804e-001 + -1.62521839e-001 -6.89298660e-002 + <_> + 1.80877328e+000 -4.66596544e-001 6.16699398e-001 3.16278100e-001 + -2.43471622e-001 -7.66116977e-002 + <_> + 1.68791354e+000 -2.57938892e-001 3.13103080e-001 -4.93084669e-001 + -2.70276517e-001 5.48639148e-002 + <_> + 1.53009677e+000 2.42150322e-001 5.30047238e-001 -6.27200365e-001 + 8.56560320e-002 -3.22554678e-001 + <_> + 1.40674007e+000 -1.01747885e-001 3.54365408e-001 -4.71799195e-001 + -2.83484727e-001 2.08929718e-001 + <_> + 1.44619417e+000 -1.40082821e-001 3.42770845e-001 -4.21162277e-001 + -2.63113588e-001 9.84747708e-002 + <_> + 7.63986230e-001 9.68695134e-002 -4.64120418e-001 -5.18972278e-001 + 2.38240406e-001 -3.79788041e-001 + <_> + -1.33971334e-001 2.81206816e-001 -1.79406434e-001 1.58263221e-001 + -5.84493056e-002 6.97598457e-002 + <_> + -1.67432487e-001 9.98716727e-002 1.08148865e-001 2.75230676e-001 + -2.60067642e-001 2.56199718e-001 + <_> + -1.63936540e-001 -1.22798368e-001 -1.64577827e-001 + -8.16148520e-002 1.17009676e+000 -1.11008155e+000 + <_> + -1.54447556e-001 -1.43579289e-001 1.63791716e-001 7.87672848e-002 + 1.01471353e+000 -9.64057267e-001 + <_> + 3.28350179e-002 5.71187912e-003 -6.73989113e-003 -1.25172250e-002 + 1.10309434e+000 -1.19239914e+000 + <_> + -8.35298896e-002 -1.43320824e-003 1.80336177e-001 4.55651321e-002 + 5.85251451e-001 -7.66745090e-001 + <_> + 2.78549761e-001 6.59460798e-002 -2.46917963e-001 -8.12129602e-002 + 7.94308841e-001 -3.17930616e-002 + <_> + 2.62568295e-001 -3.86719708e-003 -5.00528105e-002 -5.64558469e-002 + 1.32615781e+000 -1.01128078e+000 + <_> + -1.28977120e-001 8.57301727e-002 1.71108115e-002 -7.32738823e-002 + 1.38548642e-001 4.68258977e-001 + <_> + -1.84598374e+000 3.18841505e+000 -1.83985186e+000 -2.42217734e-001 + -5.20127676e-002 -7.87991434e-002 + <_> + -1.13281167e+000 3.24038434e+000 -2.11426282e+000 -2.04961635e-002 + -7.73047912e-004 1.32294118e-001 + <_> + 5.03147244e-002 2.97363132e-001 3.23701352e-001 4.38421071e-002 + -1.65275192e+000 1.09303248e+000 + <_> + 5.26262224e-001 2.06646562e+000 6.53699756e-001 1.36492625e-001 + -2.47987807e-001 9.72561389e-002 + <_> + 9.67250228e-001 6.32207811e-001 -1.88995212e-001 -1.31912604e-001 + -1.46005809e+000 7.15391457e-001 + <_> + 5.82197249e-001 3.14473081e+000 -6.54438436e-001 9.72216189e-001 + -8.76905322e-001 1.45136094e+000 + <_> + 7.74474025e-001 1.82051897e+000 8.34600329e-001 -2.48027653e-001 + -4.77010638e-001 2.09873736e-001 + <_> + 4.06900734e-001 3.11591244e+000 -8.52017879e-001 6.67743742e-001 + -9.99060750e-001 7.92381585e-001 + <_> + -1.38402009e+000 -2.96870899e+000 3.67802352e-001 7.43294954e-001 + 2.65905881e+000 -1.98194826e+000 + <_> + -2.09419560e+000 -2.42736316e+000 1.52734876e+000 6.28657877e-001 + 2.70645046e+000 -2.23724794e+000 + <_> + -1.62224352e+000 -7.93691695e-001 1.10946989e+000 2.28667915e-001 + 2.73207259e+000 -2.11092114e+000 + <_> + -1.96398854e-001 5.14182568e-001 2.31519413e+000 6.71412885e-001 + 2.36915016e+000 -9.59572017e-001 + <_> + -1.36504209e+000 -2.87219524e+000 -8.36501360e-001 1.71433258e+000 + -6.47593260e-001 1.48324990e+000 + <_> + -1.03242826e+000 1.27188230e+000 -9.34065461e-001 -4.50715810e-001 + -2.16081166e+000 3.33761668e+000 + <_> + -4.38617647e-001 2.21635628e+000 1.63177621e+000 -9.38337862e-001 + -7.06429183e-001 -1.67707317e-002 + <_> + -1.77920312e-001 2.71100593e+000 -4.28039938e-001 -9.84025657e-001 + 1.05337942e+000 -8.72183740e-001 + <_> + 5.10780156e-001 2.79258311e-001 -8.10942709e-001 -1.07312870e+000 + -4.10553098e-001 1.97385233e-002 + <_> + -2.06886679e-001 -4.06620614e-002 -1.56682730e+000 + -1.66546357e+000 1.15013433e+000 -1.35961545e+000 + <_> + -1.27478868e-001 -1.47776499e-001 8.46250236e-001 -2.52144337e-001 + -3.86511505e-001 1.06850527e-001 + <_> + -1.89892232e+000 2.39826486e-001 -3.12449789e+000 -3.11965406e-001 + 6.14602447e-001 -8.45774174e-001 + <_> + -1.62124467e+000 2.03956172e-001 -9.27810848e-001 2.54404750e-002 + -1.18018496e+000 9.28232551e-001 + <_> + 1.12806356e+000 -1.97631463e-001 4.64198828e-001 -5.61478436e-001 + -1.38038826e+000 5.79376817e-001 + <_> + -9.80130732e-002 -1.82318896e-001 2.95775801e-001 -3.47294241e-001 + -4.75651562e-001 -2.11551160e-001 + <_> + -7.45403245e-002 -5.91715500e-002 -2.61024386e-001 + -1.00150728e+000 -5.46032369e-001 -1.08663619e-001 + <_> + -9.90119129e-002 1.53233185e-001 1.13052666e+000 -1.62625104e-001 + -1.33782244e+000 7.20753312e-001 + <_> + -2.66817093e-001 -1.45727605e-001 -3.63685995e-001 + -2.30216011e-001 -7.79591426e-002 -7.41655678e-002 + <_> + 1.09745726e-001 -7.54919946e-002 2.50639248e+000 -4.32999671e-001 + -9.02351677e-001 6.09672368e-001 + <_> + -4.35121715e-001 -8.01355466e-002 7.55473137e-001 -5.28270528e-002 + 1.64965999e+000 -7.59115577e-001 + <_> + -3.44726652e-001 -9.34191048e-002 1.10419047e+000 3.49809080e-002 + 1.18209517e+000 -4.79103625e-001 + <_> + -1.97383535e+000 1.65527070e+000 -2.12590247e-001 -2.43370438e+000 + -4.21696603e-001 -1.28242314e-001 + <_> + 5.78060299e-002 1.30518913e-001 9.63202775e-001 1.66888759e-001 + 5.21834850e-001 3.13653350e-001 + <_> + -7.45403245e-002 -1.83199327e-002 7.70947516e-001 1.39761358e-001 + 5.91036499e-001 -1.60366982e-001 + <_> + 5.86632779e-003 9.94689018e-002 1.10558748e+000 5.66914342e-002 + -4.27261919e-001 4.63233471e-001 + <_> + -1.63437113e-001 -4.04098555e-002 1.44216204e+000 1.97974995e-001 + 2.63543636e-001 1.97500885e-002 + <_> + -6.10559769e-002 1.21031906e-002 1.36764991e+000 3.22762802e-002 + 5.55631697e-001 -2.47723579e-001 + <_> + -1.12482095e+000 -2.01752603e-001 3.64578843e-001 3.31332356e-001 + 5.43617487e-001 -7.08946660e-002 + <_> + -1.94037414e+000 1.75357556e+000 -3.84935230e-001 -5.19473135e-001 + 2.75899744e+000 -2.10647750e+000 + <_> + -1.98232543e+000 5.01782179e-001 -1.02485344e-001 -8.30539644e-001 + 1.60115764e-001 2.94712931e-002 + <_> + -3.70838083e-002 1.42811328e-001 -8.01232636e-001 5.68134844e-001 + -1.27918184e+000 1.56950581e+000 + <_> + -5.65611981e-002 -4.05001551e-001 6.01710021e-001 7.34161258e-001 + -2.68267214e-001 2.89472365e+000 + <_> + -1.02008432e-001 6.67168140e-001 3.31152529e-001 6.18252754e-001 + -1.53432131e+000 2.06474519e+000 + <_> + -2.66817093e-001 -5.55162370e-001 8.60765219e-001 5.37215650e-001 + 3.15743238e-001 6.46367729e-001 + <_> + -2.79302597e-001 -8.10730308e-002 1.07629132e+000 4.84755814e-001 + 1.14814985e+000 -8.91425833e-002 + <_> + -7.02311456e-001 -7.77727902e-001 4.65835601e-001 4.18527097e-001 + 7.23163486e-001 6.76250994e-001 + <_> + -6.53368294e-001 -7.55913556e-001 5.46504676e-001 6.86334848e-001 + 4.00095791e-001 8.10735464e-001 + <_> + -2.57827550e-001 -2.94095814e-001 7.86938429e-001 4.03308660e-001 + 1.41509306e+000 -3.73789817e-001 + <_> + -1.00995433e+000 -4.22538891e-002 3.43551010e-001 4.01152968e-001 + -1.21731031e+000 1.76816046e+000 + <_> + -1.17526245e+000 -2.42346659e-001 1.09409833e+000 5.38069129e-001 + -8.96628261e-001 1.40992463e+000 + <_> + -1.79604173e+000 8.40659380e-001 8.24065328e-001 1.83200344e-001 + 2.12563559e-001 -1.33770072e+000 + <_> + -2.13864398e+000 5.83573520e-001 -7.56082654e-001 5.69019675e-001 + -2.20118791e-001 -2.90861074e-003 + <_> + 4.18387413e-001 -3.97210330e-001 9.09428656e-001 1.92736581e-001 + -1.10470932e-002 -1.04559168e-001 + <_> + -4.97549236e-001 -3.27929401e+000 1.08562671e-001 -2.96887457e-001 + 1.66017985e+000 -1.31841111e+000 + <_> + -3.19256216e-001 -2.69389343e+000 1.49680033e-001 1.73826888e-001 + 1.32667446e+000 -1.04708540e+000 + <_> + 1.07248627e-001 -6.47921348e-003 1.04113564e-001 -6.06390834e-002 + 4.11455274e-001 -9.74023268e-002 + <_> + 9.74741518e-001 2.39373493e+000 2.88506448e-001 2.75788903e-001 + 1.40826559e+000 -2.41352350e-001 + <_> + -2.65818268e-001 1.77307844e+000 5.34122050e-001 2.80962229e-001 + 5.94597518e-001 2.74981499e+000 + <_> + -1.44644761e+000 3.49072671e+000 -4.91837740e-001 2.98617482e-001 + 8.40217531e-001 -7.33999789e-001 + <_> + -2.14663482e+000 -3.93329740e+000 5.85471332e-001 1.07014179e+000 + -1.81862676e+000 1.73318124e+000 + <_> + -7.59245396e-001 1.38869321e+000 3.06857753e+000 2.72080779e-001 + -5.12226880e-001 1.04752564e+000 + <_> + -8.49141002e-001 1.09119631e-001 6.86577857e-001 6.17093563e-001 + -4.34687376e-001 1.09432793e+000 + <_> + -1.19823575e+000 -8.84153992e-002 5.91064334e-001 5.90250909e-001 + -3.59216183e-002 6.78513944e-001 + <_> + -1.19024503e+000 1.61022529e-001 7.00304687e-001 7.90022254e-001 + 8.25902969e-002 6.43253267e-001 + <_> + -2.03276682e+000 -5.22139549e-001 -1.58381200e+000 2.83385336e-001 + 3.65431488e-001 1.56423122e-001 + <_> + -1.55232465e+000 -2.39714932e+000 2.31612992e+000 9.08502877e-001 + 5.53913534e-001 1.31580651e-001 + <_> + -1.57579744e+000 2.66825581e+000 1.35412073e+000 -1.83244005e-001 + 2.63201141e+000 -2.12314391e+000 + <_> + 1.24542725e+000 1.56991625e+000 8.02165031e-001 5.51528752e-001 + 1.44929841e-001 -1.70772687e-001 + <_> + -6.45877004e-001 -3.76056939e-001 2.80380517e-001 -5.71690738e-001 + 9.98591363e-001 -7.04524815e-001 + <_> + -7.35273242e-001 -2.06158787e-001 6.57689869e-002 -6.75012052e-001 + -4.08538520e-001 3.61850291e-001 + <_> + -1.80203474e+000 2.34680009e+000 -2.13821626e+000 8.86861563e-001 + -1.04781783e+000 2.43161058e+000 + <_> + -1.53084958e+000 3.10490894e+000 2.99468458e-001 -5.40282428e-002 + 2.20288777e+000 1.77248526e+000 + <_> + 1.80663392e-001 3.65916395e+000 1.90487194e+000 6.81701005e-002 + 1.54919839e+000 -4.74205524e-001 + <_> + -1.83399761e+000 3.12070274e+000 1.70866573e+000 1.13422394e+000 + 7.02312291e-001 1.10355723e+000 + <_> + -1.91046819e-002 3.47086763e+000 2.27924275e+000 -2.35527992e-001 + 8.09948266e-001 3.49594980e-001 + <_> + -1.98032773e+000 3.11156631e+000 1.23407650e+000 9.56514597e-001 + -6.49715424e-001 1.80417967e+000 + <_> + -1.49239421e+000 3.00763154e+000 -3.51780415e-001 -3.36108059e-001 + -1.06246173e+000 -4.99490276e-002 + <_> + -9.03078377e-001 9.32131052e-001 -3.41147327e+000 -1.18708456e+000 + -1.19405365e+000 -3.30485612e-001 + <_> + -7.77723908e-001 3.52273250e+000 -9.76405799e-001 -1.75976232e-001 + -7.09887087e-001 4.62080300e-001 + <_> + -6.32392645e-001 2.72503901e+000 -1.94142222e+000 -4.50175375e-001 + -8.97263825e-001 1.11183062e-001 + <_> + 1.87655285e-001 6.65919036e-002 5.36533773e-001 -4.32785928e-001 + 3.50248247e-001 -9.98563051e-001 + <_> + -8.05333629e-002 2.61521220e-001 1.78306669e-001 -2.98594713e-001 + -1.89702547e+000 1.19672143e+000 + <_> + -2.06822586e+000 2.18264627e+000 9.94965911e-001 -1.48155320e+000 + 1.62243128e+000 -9.86042202e-001 + <_> + -1.88094318e+000 3.63989329e+000 -1.47011197e+000 4.25408393e-001 + 1.05839193e+000 3.95391971e-001 + <_> + -1.00945485e+000 3.51749134e+000 3.86547625e-001 1.26659966e+000 + -4.10611570e-001 2.41027907e-001 + <_> + -6.24901354e-001 2.85088205e+000 -1.45558381e+000 6.16363823e-001 + 1.02889538e+000 -6.22767210e-001 + <_> + -6.15412354e-001 3.57180810e+000 -3.44624400e-001 1.19941974e+000 + 7.62020350e-001 -5.05140960e-001 + <_> + 2.09130347e-001 2.27400158e-002 1.24367058e+000 -1.62673980e-001 + 9.51206386e-002 -4.59937990e-001 + <_> + -6.75484389e-002 -2.29210183e-001 1.30199397e+000 -1.32440582e-001 + 1.44101575e-001 -7.76825011e-001 + <_> + -1.90905228e-001 -1.66031271e-001 7.10481226e-001 -4.15381640e-002 + -3.71236593e-001 3.65892708e-001 + <_> + -4.00803313e-002 -1.60316080e-001 1.32683837e+000 -3.90556678e-002 + 3.60997505e-002 1.28445134e-001 + <_> + 8.02799389e-002 4.28145081e-002 1.54612041e+000 5.34252264e-002 + 2.48926133e-001 -5.35089910e-001 + <_> + -2.28361741e-001 -9.11202133e-002 8.64315510e-001 -6.68809786e-002 + -1.97687805e-001 2.31446341e-001 + <_> + -1.21985242e-001 -6.96723238e-002 7.76532590e-001 -1.02615193e-001 + -2.01690048e-002 1.86061442e-001 + <_> + -1.33557630e+000 1.23541750e-001 -1.31467772e+000 6.59569204e-001 + -9.91667733e-002 -2.54178852e-001 + <_> + -1.24218476e+000 -1.65727317e+000 1.82858086e+000 5.38139492e-002 + 1.30497682e+000 -1.32239640e+000 + <_> + -1.90091991e+000 5.13732135e-001 2.37814069e+000 3.33435237e-001 + 1.23597252e+000 -1.72638309e+000 + <_> + -8.11684489e-001 2.38455629e+000 2.75171304e+000 7.78975524e-003 + 2.08956742e+000 -1.50553894e+000 + <_> + -1.42247546e+000 3.14397144e+000 2.07149482e+000 -4.21942085e-001 + 2.03765798e+000 -1.41131127e+000 + <_> + -6.97816730e-001 1.68020332e+000 1.16456711e+000 4.18830961e-001 + 2.17348218e+000 1.61164141e+000 + <_> + -5.66968620e-001 2.44328237e+000 7.93098152e-001 1.30720401e+000 + 1.92777491e+000 -8.59223425e-001 + <_> + -7.43763387e-001 1.68052030e+000 9.10583079e-001 1.48927307e+000 + 8.09036791e-001 6.12018287e-001 + <_> + -1.22484662e-001 3.08858216e-001 7.74489880e-001 -4.44620401e-001 + -2.29554796e+000 1.91437769e+000 + <_> + -3.38733613e-001 1.97184816e-001 9.82240617e-001 -7.03394353e-001 + -2.29311419e+000 1.89257967e+000 + <_> + 4.00408298e-001 5.85335791e-001 -1.14797318e+000 -5.33723116e-001 + -9.66186523e-001 1.30104935e+000 + <_> + 2.93390770e-002 2.12619230e-001 1.75589234e-001 -5.28005540e-001 + -9.59907472e-001 6.50747240e-001 + <_> + 9.02683437e-002 2.85396785e-001 8.17520395e-002 -4.38852310e-001 + -1.06877387e+000 7.14891195e-001 + <_> + 3.53321210e-002 3.92944306e-001 1.04519464e-001 -5.07149696e-001 + -1.62387586e+000 9.26096141e-001 + <_> + -2.05887839e-001 3.32450628e-001 -6.38956189e-001 -7.06774354e-001 + -1.28666043e+000 7.62724698e-001 + <_> + 8.97689238e-002 3.04553360e-001 -3.58815163e-001 1.18312806e-001 + -2.57179928e+000 1.57111490e+000 + <_> + 4.93158847e-002 -2.29309704e-002 3.16263318e-001 -8.90398741e-001 + -1.50310910e+000 8.91582727e-001 + <_> + -5.35646752e-002 1.96318358e-001 -6.40150130e-001 -4.13506597e-001 + -2.16060019e+000 1.27797711e+000 + <_> + -6.10559769e-002 -1.89875364e-002 5.31966031e-001 2.15421095e-001 + 1.04482841e+000 -9.84939262e-002 + <_> + 9.82590616e-002 7.49582499e-002 -1.25841546e+000 -3.67843330e-001 + 1.51332349e-001 -8.98037925e-002 + <_> + 1.51861012e+000 -9.98454541e-002 1.25370711e-001 -3.94611865e-001 + 1.00811116e-001 -2.16482729e-002 + <_> + 6.20652616e-001 3.39444220e-001 -1.62850332e+000 -5.51012158e-001 + 3.65591571e-002 -2.78836358e-002 + <_> + 1.53159511e+000 2.92242944e-001 7.93124497e-001 -9.02108669e-001 + -5.23223206e-002 -8.72540623e-002 + <_> + 5.92685103e-001 3.74590755e-001 -1.08560133e+000 6.28126502e-001 + 7.77690262e-002 -1.39243171e-001 + <_> + 1.82375586e+000 -3.79394650e-001 9.20266747e-001 -1.77554667e-001 + -2.08838269e-001 9.39903036e-002 + <_> + 1.77681041e+000 4.81674582e-001 5.78918874e-001 -3.94625723e-001 + -2.25329325e-001 3.60907838e-002 + <_> + 1.65595067e+000 3.82014871e-001 9.33021069e-001 1.98058337e-001 + -2.23243564e-001 7.24935606e-002 + <_> + 7.49003589e-001 2.52013475e-001 -7.62071013e-001 -9.21717703e-001 + -2.01634631e-001 2.80817878e-002 + <_> + 1.85671759e+000 -3.92280161e-001 2.80569904e-002 -9.29338694e-001 + -1.07707274e+000 8.13790321e-001 + <_> + 1.77780926e+000 6.41132593e-001 1.31118214e+000 -1.07554638e+000 + -3.40006083e-001 -1.29238963e-002 + <_> + -1.01151187e-002 1.70951962e-001 8.85307603e-003 8.03848635e-003 + 8.66323709e-001 -9.76633728e-001 + <_> + -1.19987562e-001 5.59314042e-002 2.20844686e-001 1.70829490e-001 + 2.25221068e-001 -5.49149692e-001 + <_> + -4.35762703e-002 -2.48914808e-002 5.10753691e-001 1.87470969e-002 + 4.57740128e-001 -6.90135658e-002 + <_> + 6.82938546e-002 -6.98541403e-002 5.20026207e-001 1.04434462e-002 + 3.13630760e-001 1.93843115e-002 + <_> + 1.73172101e-001 6.28466979e-002 1.76986173e-001 -1.84501261e-001 + -1.39706567e-001 -1.66895911e-002 + <_> + -6.05565570e-002 -4.17369464e-003 -8.17368999e-002 1.74467549e-001 + 5.42129338e-001 -9.86908656e-003 + <_> + -1.57943502e-001 -4.92282100e-002 -9.57800522e-002 6.66780546e-002 + 4.64954019e-001 1.85474023e-001 + <_> + -1.17989883e-001 -8.73178765e-002 9.01679248e-002 1.01438187e-001 + 8.96561563e-001 -8.53621662e-001 + <_> + 2.08630934e-001 9.40930188e-001 -1.28380811e+000 -8.15462321e-002 + -1.16571784e+000 2.76450396e+000 + <_> + 8.42752978e-002 6.40765786e-001 -1.36450362e+000 -2.64073551e-001 + -1.78643084e+000 1.55651677e+000 + <_> + -7.98200190e-001 2.77897859e+000 -1.57491374e+000 6.87539160e-001 + -4.03040737e-001 2.22172642e+000 + <_> + -1.11233544e+000 1.69142890e+000 1.99808979e+000 -9.17069077e-001 + -1.69565010e+000 9.02652442e-001 + <_> + -1.54832935e+000 1.48901260e+000 2.38595819e+000 1.21284679e-001 + -5.91189742e-001 1.63454807e+000 + <_> + -1.92589092e+000 5.76874435e-001 3.70063782e-001 -1.96581021e-001 + -6.12470508e-001 3.77894822e-003 + <_> + -2.09968925e+000 2.31422544e+000 -3.52984935e-001 -2.41298378e-001 + 2.22434449e+000 -1.40442598e+000 + <_> + -1.61225510e+000 2.43410110e+000 1.15176797e+000 -2.34011126e+000 + 2.80747509e+000 -3.29028368e+000 + <_> + 1.06249787e-001 -2.79930737e-002 2.97707796e-001 1.00182690e-001 + -2.03398764e-001 3.43318999e-001 + <_> + -9.21556950e-001 1.33858979e-001 -1.83507323e+000 1.32781029e-001 + -6.33127928e-001 -6.94636106e-002 + <_> + 8.36342853e-003 4.45885688e-001 9.66043770e-002 7.02789426e-001 + -1.39026165e-001 5.25931299e-001 + <_> + -1.72362578e+000 1.63198066e+000 4.04665619e-001 -3.01836085e+000 + -7.67068118e-002 -4.95339155e-001 + <_> + -2.12316203e+000 2.87480974e+000 7.62826800e-001 -6.64038181e-001 + 7.55380511e-001 -9.69499767e-001 + <_> + -2.10468364e+000 2.68706608e+000 -1.44788772e-001 -1.67257857e+000 + 6.61190629e-001 -7.40668774e-001 + <_> + -1.94886434e+000 2.30789185e+000 -7.99160957e-001 -2.10632920e+000 + 7.55716741e-001 -8.61907959e-001 + <_> + -2.12466025e+000 1.39981735e+000 -2.72236764e-001 -8.79008412e-001 + 7.42857993e-001 -4.18920398e-001 + <_> + -1.58728409e+000 3.44155639e-001 1.27842534e+000 -3.98361981e-001 + -1.45508683e+000 1.92186189e+000 + <_> + -2.01928258e+000 1.64219987e+000 1.50659502e+000 -9.54031110e-001 + -1.04917145e+000 1.11423051e+000 + <_> + -1.73361421e+000 1.09804280e-001 3.03153217e-001 -7.66446888e-001 + 1.94886386e-001 2.97181457e-002 + <_> + -2.05574036e+000 1.44381237e+000 1.60097516e+000 -8.24845731e-001 + 7.77872980e-001 -8.76015007e-001 + <_> + 3.75437289e-001 3.99262697e-001 5.90531528e-002 -2.02273622e-001 + -4.64561522e-001 6.17038071e-001 + <_> + -2.91788101e-001 2.87263870e+000 -1.22845781e+000 -3.87931824e-001 + 1.66350722e-001 4.37727839e-001 + <_> + -1.94986320e+000 -2.97395921e+000 2.72357345e-001 7.85680294e-001 + 8.08755681e-002 -2.47405007e-001 + <_> + -1.88044369e+000 -2.06368804e+000 -7.25408018e-001 4.41940010e-001 + 1.58360076e+000 -1.69498241e+000 + <_> + -2.02427673e+000 -1.44647062e+000 -6.68449938e-001 2.16369733e-001 + 1.87347698e+000 -1.64001501e+000 + <_> + -8.33159566e-001 -4.91252065e-001 1.03585398e+000 8.54440272e-001 + 2.98758358e-001 1.05232380e-001 + <_> + -1.15678382e+000 -2.69980371e-001 6.47540092e-001 9.59584296e-001 + 4.81530547e-001 -1.90048307e-001 + <_> + -1.67917740e+000 1.01632379e-001 2.53316481e-002 1.04846191e+000 + 2.40951404e-001 2.20974907e-001 + <_> + -6.37886286e-001 -5.95273435e-001 1.02337122e+000 1.11077762e+000 + 1.67037582e+000 -2.47664124e-001 + <_> + -1.22120905e+000 -8.53964984e-001 7.28746831e-001 1.05854726e+000 + 2.97325104e-001 2.68220574e-001 + <_> + -1.23768997e+000 -8.45602751e-001 6.39074087e-001 1.00957477e+000 + 7.41299987e-001 1.71311572e-001 + <_> + -5.12531817e-001 -1.38108635e+000 1.70449186e-002 1.19489431e+000 + 1.28325677e+000 -4.46905762e-001 + <_> + -1.56331193e+000 2.00016528e-001 8.75809908e-001 7.19201148e-001 + 9.41861331e-001 -8.87463614e-002 + <_> + 1.21232390e-001 3.69459510e-001 1.25464046e+000 3.44072193e-001 + -9.30804312e-001 2.42072034e+000 + <_> + -1.15678382e+000 3.59658957e+000 1.93336153e+000 8.38249862e-001 + 5.02469301e-001 8.92281115e-001 + <_> + -6.05423987e-001 -2.67190009e-001 1.14197624e+000 5.87562084e-001 + -3.71840268e-001 5.16881645e-001 + <_> + -7.40409046e-002 -2.71534038e+000 2.51299763e+000 4.77559716e-001 + 7.19691575e-001 -2.56870687e-001 + <_> + 2.72556722e-001 2.81360412e+000 1.41981626e+000 3.98389906e-001 + 1.89270169e-001 1.14481583e-001 + <_> + -6.86829448e-001 -1.34976292e+000 1.14490461e+000 -1.06582232e-002 + -3.00100565e-001 -1.08552501e-001 + <_> + -1.89143097e+000 -4.96903181e-001 2.60870844e-001 -1.90098405e-001 + -4.99627590e-001 -1.35092750e-001 + <_> + -2.00130343e+000 2.50520140e-001 6.02577209e-001 1.01704752e+000 + 1.79844534e+000 8.26755226e-001 + <_> + -9.57015812e-001 1.05929637e+000 -1.88083246e-001 1.07490933e+000 + 1.58627009e+000 8.12697828e-001 + <_> + -1.58428752e+000 1.92650169e-001 9.73223805e-001 1.19128251e+000 + 2.85347790e-001 2.64190102e+000 + <_> + -1.40399683e+000 9.58805755e-002 1.62694240e+000 9.07714069e-001 + 1.74972689e+000 2.16902685e+000 + <_> + -1.91190720e+000 5.27438615e-003 -2.13581279e-001 1.12943494e+000 + 5.58454335e-001 2.92300224e+000 + <_> + -1.58728409e+000 -4.58301514e-001 1.85913944e+000 1.11428320e+000 + 4.30840850e-001 2.95976013e-001 + <_> + -1.64172089e+000 -3.92353714e-001 1.09038186e+000 1.55036855e+000 + -8.23070109e-001 1.64995790e+000 + <_> + -2.13714576e+000 -2.03441715e+000 -1.71006334e+000 1.10618293e+000 + 7.20092416e-001 -6.86585605e-002 + <_> + -2.13015389e+000 -1.34826231e+000 9.00251031e-001 1.73763001e+000 + 8.71828318e-001 -6.67268038e-002 + <_> + -1.78505445e+000 9.41439331e-001 5.34965515e-001 9.51584220e-001 + 6.43024385e-001 2.08991423e-001 + <_> + -1.52949288e-001 2.73710799e+000 -2.15518534e-001 -3.64104257e-004 + 2.12859130e+000 -1.59831882e+000 + <_> + 6.78085923e-001 3.93828750e+000 7.16957211e-001 9.77817953e-001 + 1.12703688e-001 1.24222291e+000 + <_> + 9.53765869e-001 4.20155764e+000 2.61701107e-001 1.59436584e+000 + 2.35724941e-001 8.06082308e-001 + <_> + -2.33355954e-001 4.00099802e+000 1.06915340e-001 1.74980319e+000 + 7.54696071e-001 3.17199826e+000 + <_> + -9.01080728e-001 4.29199600e+000 -8.44777524e-001 1.30988443e+000 + 1.57211065e-001 2.58407021e+000 + <_> + -1.81252253e+000 2.22114897e+000 2.03032434e-001 8.92364860e-001 + 1.53347886e+000 7.03941286e-001 + <_> + -4.10791710e-002 -1.42959669e-001 1.14759302e+000 1.49690318e+000 + 1.81722343e+000 -5.19916058e-001 + <_> + -1.79953766e+000 3.63634324e+000 9.75875318e-001 -6.84603035e-001 + 2.04718947e-001 -8.10936689e-002 + <_> + -1.98482251e+000 3.55187011e+000 1.68206558e-001 8.28951120e-001 + 8.84562135e-001 1.88068843e+000 + <_> + -1.66719127e+000 3.24793839e+000 1.45945239e+000 1.13849306e+000 + 1.50002873e+000 6.27646744e-001 + <_> + 1.59701908e+000 4.24205208e+000 1.72494924e+000 3.98604512e-001 + 1.08353746e+000 1.32306349e+000 + <_> + -1.84148896e+000 3.90223646e+000 2.18047690e+000 5.93671143e-001 + 1.93397033e+000 1.53940129e+000 + <_> + 1.18449807e+000 4.13835621e+000 1.55299410e-001 1.01870096e+000 + 1.28040421e+000 -3.93731833e-001 + <_> + -1.96434629e+000 3.65036225e+000 -1.24133599e+000 -3.07657178e-002 + 1.89526176e+000 1.09744239e+000 + <_> + 1.19448638e+000 4.51626015e+000 1.86028337e+000 2.03806311e-002 + 1.50220537e+000 7.32011855e-001 + <_> + -2.06622815e+000 3.66354108e+000 1.34212816e+000 2.98633635e-001 + 1.86659288e+000 -8.67449224e-001 + <_> + -4.40615326e-001 2.57166290e+000 1.09662330e+000 9.86583680e-002 + 2.53397942e-001 2.44818544e+000 + <_> + 2.10628614e-001 3.92139006e+000 -2.32772022e-001 -4.94373679e-001 + 1.17658246e+000 1.15391695e+000 + <_> + -1.15129030e+000 2.34678316e+000 -6.33057415e-001 1.60264432e-001 + -1.26042473e+000 2.58849573e+000 + <_> + -1.85911030e-001 3.96050006e-001 8.45456898e-001 8.09389055e-001 + 3.71388316e-001 -4.73024510e-002 + <_> + 3.81929725e-001 1.56918336e-002 -4.66742963e-001 -4.65144873e-001 + -1.17514336e+000 1.64761817e+000 + <_> + 1.23587903e-002 3.51167500e-001 -5.42391002e-001 -9.72994566e-002 + -1.12724334e-001 5.50321117e-002 + <_> + 4.37365383e-001 -4.66935299e-002 -7.44435370e-001 -9.00770128e-001 + -1.02762675e+000 1.37772453e+000 + <_> + -3.35878693e-002 1.13812104e-001 -9.16050255e-001 -9.08745468e-001 + -7.34673083e-001 4.31091845e-001 + <_> + -1.40963212e-001 3.48104417e-001 -1.94996759e-001 -2.30056465e-001 + -1.35510981e+000 1.85725915e+000 + <_> + -9.45171341e-002 3.92526060e-001 -5.36294915e-002 -2.45089158e-001 + -1.40147722e+000 1.86099863e+000 + <_> + -2.95783460e-001 2.25587457e-001 -8.57853740e-002 -9.29296553e-001 + -1.17426193e+000 1.57627070e+000 + <_> + -1.70314956e+000 2.03803825e+000 -1.90838075e+000 -6.25289619e-001 + 1.26844263e+000 -9.10359323e-001 + <_> + -1.74709857e+000 2.74822879e+000 1.39252067e+000 -5.08573890e-001 + -2.13658035e-001 1.33886290e+000 + <_> + -1.74210429e+000 2.11013627e+000 1.84138250e+000 -1.48611653e+000 + 1.37119865e+000 -1.08953583e+000 + <_> + -1.22720206e+000 8.72464716e-001 4.38397303e-002 -3.02316594e+000 + 1.69293737e+000 -1.45531607e+000 + <_> + -2.01528716e+000 1.69094145e+000 -2.08330914e-001 -2.40734744e+000 + 1.79972279e+000 -1.32653916e+000 + <_> + -2.04375410e+000 -1.26938701e+000 6.01609886e-001 1.03139198e+000 + -1.87217534e+000 1.69909143e+000 + <_> + -2.01328945e+000 3.82009834e-001 -2.85995245e+000 -1.68296859e-001 + -5.82950354e-001 8.54553819e-001 + <_> + 2.03495137e-002 -8.04395154e-002 1.29700196e+000 -1.83148891e-001 + 5.08031785e-001 -6.41861558e-001 + <_> + 1.39211521e-001 -1.73655197e-001 1.46557784e+000 -2.07808495e-001 + -2.63250411e-001 3.97780053e-002 + <_> + 3.98268998e-002 -2.99497873e-001 1.32199121e+000 1.17988274e-001 + -2.27201760e-001 2.13933930e-001 + <_> + 3.05518448e-001 -1.43324867e-001 1.31039405e+000 -1.04312021e-002 + 1.45351617e-002 2.09697708e-002 + <_> + 6.86516846e-003 -2.41584048e-001 1.06138098e+000 9.20396373e-002 + 4.56773430e-001 -8.17769617e-002 + <_> + -1.06003799e-001 -1.25671839e-002 7.49830008e-001 -1.46916941e-001 + -3.60406399e-001 1.12733759e-001 + <_> + -6.30536601e-002 -1.16788052e-001 9.11785007e-001 1.23396860e-002 + -7.08381832e-002 -3.41393083e-001 + <_> + -8.05333629e-002 -8.87411535e-002 1.13743758e+000 1.57107681e-001 + -3.37333828e-001 2.50284284e-001 + <_> + -7.35414848e-002 -1.84671119e-001 9.60893869e-001 -6.59539402e-002 + 1.25416130e-001 -3.61980975e-001 + <_> + -1.94986320e+000 4.63924557e-001 -1.99158955e-002 7.16060027e-002 + 3.23804058e-002 -2.85197139e-001 + <_> + 1.63183689e-001 4.12036806e-001 2.24956989e+000 1.98920935e-001 + 5.33733070e-001 -5.48337340e-001 + <_> + -1.73211598e+000 5.81807382e-002 9.51305091e-001 4.26007926e-001 + 5.66157401e-001 -1.30951360e-001 + <_> + -1.87095475e+000 -5.21777451e-001 1.29902351e+000 8.56526017e-001 + -8.13893557e-001 1.15156829e+000 + <_> + -1.71613443e+000 -1.05453813e+000 2.58400154e+000 7.71585345e-001 + 1.23817039e+000 -1.34679818e+000 + <_> + -1.53035021e+000 2.91018796e+000 -3.17012519e-001 -1.25364006e-001 + 2.18048000e+000 -1.40670180e+000 + <_> + -2.07022357e+000 1.62476134e+000 1.19243991e+000 -2.97319442e-001 + 1.69257689e+000 -7.88755715e-001 + <_> + -1.89193034e+000 8.42557073e-001 -2.84661591e-001 2.12549612e-001 + 6.72586679e-001 -5.38844585e-001 + <_> + -8.89594078e-001 3.41943359e+000 1.20385528e+000 -2.87655294e-001 + 1.06207430e+000 -1.53620505e+000 + <_> + -1.55232465e+000 -9.93080497e-001 -2.79785879e-002 1.75678730e-002 + -1.80857086e+000 8.61705959e-001 + <_> + -1.68816698e+000 2.20141745e+000 2.04322934e+000 4.24376041e-001 + 9.25112888e-002 1.69961619e+000 + <_> + -2.06872535e+000 -1.65011990e+000 9.45419490e-001 1.15381086e+000 + -4.19510692e-001 2.77176762e+000 + <_> + -1.09185922e+000 1.17656934e+000 -1.37612975e+000 -2.53090167e+000 + -1.80237865e+000 7.87102818e-001 + <_> + -1.28477708e-001 7.64304519e-001 3.67778629e-001 -2.13195992e+000 + -2.33401322e+000 1.76424992e+000 + <_> + -1.52335835e+000 1.63805532e+000 4.72301066e-001 -1.64143607e-001 + -1.68854630e+000 2.88552332e+000 + <_> + -4.18640852e-001 3.59527856e-001 -5.13901412e-001 -1.85568488e+000 + -1.93663430e+000 1.61863780e+000 + <_> + -1.25481188e-001 9.85935450e-001 2.47262597e-001 -2.31945723e-001 + -2.89144826e+000 2.46070719e+000 + <_> + -2.10368466e+000 1.46177888e+000 -1.05216455e+000 -1.38222373e+000 + -1.27137280e+000 1.24567699e+000 + <_> + 1.14104843e+000 1.16464846e-001 2.67742187e-001 -1.85707355e+000 + 1.45580733e+000 -2.06808329e+000 + <_> + 9.04323280e-001 2.00599223e-001 1.10916376e-001 -1.03506351e+000 + 8.70622456e-001 -8.14173758e-001 + <_> + 7.78469384e-001 1.13974355e-001 -6.00843914e-002 -9.41020727e-001 + 6.86240733e-001 -7.17650056e-001 + <_> + 1.50312817e+000 -1.97779275e-002 1.52024281e+000 -2.29066753e+000 + 8.53797019e-001 -1.87799084e+000 + <_> + -9.58514035e-001 4.04426977e-002 -7.47590303e-001 -5.09176612e-001 + 7.15873361e-001 -1.78208280e+000 + <_> + 7.15542436e-001 9.21878695e-001 -8.51440668e-001 -1.80357122e+000 + -2.47301459e+000 -1.45148838e+000 + <_> + -4.69581693e-001 -9.34661403e-002 -1.88180774e-001 + -1.08244903e-002 6.96903110e-001 1.12126410e+000 + <_> + -2.83797383e-001 1.01961613e+000 3.21873498e+000 -1.22643805e+000 + 6.79650247e-001 3.25656265e-001 + <_> + -9.52521026e-001 9.46370125e-001 1.98372245e+000 -1.05382836e+000 + 3.02586794e+000 1.90454149e+000 + <_> + -3.65843885e-002 5.40252864e-001 -1.64335573e+000 -1.60747898e+000 + 2.31576777e+000 -3.91465259e+000 + <_> + -5.60617782e-002 4.42065358e-001 -7.67342448e-001 -2.07220984e+000 + 2.95032740e+000 -2.46239042e+000 + <_> + -1.26480028e-001 3.14137816e-001 -1.42376268e+000 -1.52204013e+000 + 8.56187791e-002 -2.98900080e+000 + <_> + -1.72426686e-001 3.92603725e-001 -7.73180604e-001 -1.90389192e+000 + 3.20244217e+000 -4.05943727e+000 + <_> + -3.97665203e-001 2.34538063e-001 -8.87637079e-001 -1.90969610e+000 + 2.82496428e+000 -4.28257370e+000 + <_> + 6.39630616e-001 6.53769016e-001 3.35132480e-001 -2.26755404e+000 + 2.30602789e+000 -3.66040373e+000 + <_> + 6.22150898e-001 6.48752093e-001 5.24137914e-001 -1.43440330e+000 + 2.76068044e+000 -3.30256581e+000 + <_> + -1.83413923e-001 9.92242813e-001 4.98646265e-003 1.25081837e+000 + 5.36409795e-001 -3.16081315e-001 + <_> + 1.33332527e+000 6.04951322e-001 1.42697990e-001 -1.46513057e+000 + 9.56014931e-001 -1.98001289e+000 + <_> + 1.07462561e+000 7.19264209e-001 -6.23310506e-001 -8.81133497e-001 + 7.45695531e-001 -1.51757944e+000 + <_> + -2.09269738e+000 1.00015807e+000 -1.36248457e+000 5.89154065e-002 + 2.25757286e-001 3.64918888e-001 + <_> + -2.09383771e-001 2.37921849e-001 7.62204766e-001 -2.36239505e+000 + -9.40445662e-001 -1.71383989e+000 + <_> + -1.66269648e+000 4.52414006e-001 -1.85533142e+000 -2.12784100e+000 + -6.55608714e-001 -1.59612358e+000 + <_> + -1.29476547e-001 -1.55667156e-001 -1.17783107e-001 + -6.53223991e-001 -8.82478833e-001 -1.29903662e+000 + <_> + -2.01892480e-001 -4.30446863e-001 -1.36929415e-002 + -9.57119226e-001 -1.36791289e+000 -1.35026181e+000 + <_> + -3.81683737e-001 3.47821593e-001 9.93639827e-002 4.26398814e-001 + 2.17185211e+000 1.44645894e+000 + <_> + -3.00278246e-001 2.31122091e-001 2.02012405e-001 3.07893604e-001 + 2.44041061e+000 1.06671596e+000 + <_> + 1.26340640e+000 1.18949556e+000 9.18181896e-001 -1.49215615e+000 + 6.04823604e-002 -9.72620010e-001 + <_> + -6.02926850e-001 4.48832929e-001 -5.50240219e-001 6.38573825e-001 + 1.17677927e+000 5.54674029e-001 + <_> + -1.18624961e+000 -1.15428537e-001 -2.27231711e-001 + -4.25142013e-002 -5.65268360e-002 2.35013515e-001 + <_> + -1.90405816e-001 9.19319969e-003 1.08520532e+000 -2.60393715e+000 + 2.21289730e+000 -2.11499023e+000 + <_> + -7.16794670e-001 9.93895531e-001 9.13977921e-001 -1.26987576e+000 + 3.77165318e+000 -3.15211868e+000 + <_> + -8.30163062e-001 1.05660093e+000 3.88669401e-001 -1.21635520e+000 + 3.86376357e+000 -3.15917516e+000 + <_> + -1.42896783e+000 1.69968414e+000 -1.08401203e+000 -1.12145770e+000 + 2.25389791e+000 6.52678549e-001 + <_> + -6.72845662e-001 1.29132318e+000 -9.69890237e-001 -7.48398721e-001 + 3.39310074e+000 -2.65503669e+000 + <_> + -1.02843285e+000 -7.54713774e-001 2.97651935e+000 -1.73340702e+000 + 1.78213203e+000 -2.19445610e+000 + <_> + -8.64623070e-001 1.40622318e+000 -1.86616695e+000 -2.14993906e+000 + 2.46109676e+000 -2.65905094e+000 + <_> + -8.88095796e-001 1.34167159e+000 -2.22710609e+000 -1.64103520e+000 + 2.66075182e+000 -2.79945135e+000 + <_> + -1.20472825e+000 2.02803278e+000 5.68215191e-001 -1.74962163e+000 + 3.45107889e+000 -2.64803123e+000 + <_> + -9.23055232e-001 2.34303236e+000 -1.42110586e+000 -1.23974013e+000 + 1.76427102e+000 -1.24965489e+000 + <_> + -9.52021599e-001 2.70279074e+000 1.13593781e+000 -2.05728579e+000 + 1.17046690e+000 -7.10795164e-001 + <_> + -2.85795063e-001 2.52179837e+000 1.56460738e+000 -5.14180779e-001 + 3.77609134e+000 -2.76345921e+000 + <_> + -1.34306765e+000 1.56091142e+000 1.57980764e+000 -8.52848411e-001 + 2.21750140e+000 -3.04235041e-001 + <_> + -1.84848082e+000 1.52791727e+000 -3.04007268e+000 -1.31479180e+000 + 7.86536753e-001 -4.63879108e-001 + <_> + -3.82183164e-001 1.21406049e-001 -6.57827973e-001 1.36540264e-001 + -9.35007393e-001 1.32846975e+000 + <_> + -1.09685349e+000 1.40682891e-001 1.94995606e+000 -1.93820262e+000 + 2.50542331e+000 -1.92697728e+000 + <_> + -1.30011749e+000 5.39809048e-001 1.99866438e+000 -2.68590331e+000 + 1.24789643e+000 -1.18525636e+000 + <_> + -2.17110634e+000 -3.30656219e+000 4.57820356e-001 8.66111577e-001 + -1.63323486e+000 1.72593093e+000 + <_> + -1.82500803e+000 -1.13569951e+000 -1.03204334e+000 + -9.54244196e-001 1.62631249e+000 7.23832369e-001 + <_> + 1.48215246e+000 -1.37407988e-001 1.29832616e-002 -2.03158116e+000 + -7.56912231e-001 -3.31954598e+000 + <_> + -5.02543449e-001 1.64237285e+000 -4.73922670e-001 -2.56213355e+000 + 4.88338530e-001 -1.72632384e+000 + <_> + -2.34354794e-001 1.40562820e+000 -9.21456099e-001 -2.58993101e+000 + 9.27750707e-001 -2.09165859e+000 + <_> + -1.22520447e+000 8.06056678e-001 -8.39756489e-001 -2.56848001e+000 + -7.84392357e-002 -2.02933884e+000 + <_> + -1.39650559e+000 7.34344125e-001 2.23955417e+000 -2.34179306e+000 + 7.96892226e-001 -1.21538579e+000 + <_> + -6.24401927e-001 1.79618788e+000 7.23080039e-001 -2.08904743e+000 + 1.06796741e+000 -2.46390271e+000 + <_> + -1.05340385e+000 9.06079054e-001 -1.95515180e+000 -1.87957525e+000 + 3.02569151e+000 -2.64945865e+000 + <_> + -1.75608814e+000 5.79178691e-001 -7.18186140e-001 1.09571338e+000 + -2.77736813e-001 1.49902761e+000 + <_> + -1.82800460e+000 5.31076849e-001 -8.13212037e-001 1.32634723e+000 + 5.09279609e-001 2.63299632e+000 + <_> + -1.81452024e+000 2.55667776e-001 2.64489710e-001 1.09727216e+000 + -1.58583522e+000 2.44456744e+000 + <_> + -1.60940021e-001 -2.62917340e-001 1.32341719e+000 1.58299699e-001 + -1.48328289e-001 1.13679683e+000 + <_> + -1.02243984e+000 6.62950695e-001 -2.66608930e+000 -2.44034386e+000 + 1.19383359e+000 -1.31765914e+000 + <_> + -7.49256968e-001 9.81440902e-001 -2.08386755e+000 -2.63663960e+000 + 1.19467974e+000 -1.77421510e+000 + <_> + -1.90141940e+000 -1.09426582e+000 -5.09873986e-001 7.67016232e-001 + -8.87601256e-001 5.02179444e-001 + <_> + -1.33208036e+000 -8.60064030e-001 2.07402483e-001 4.80752885e-001 + 4.23306346e-001 8.04510526e-003 + <_> + -1.88593733e+000 -2.74430722e-001 1.03928041e+000 -1.82253033e-001 + 6.45167828e-001 -8.29594195e-001 + <_> + -2.03176808e+000 6.69032037e-001 -4.84563112e-001 -8.55505288e-001 + -2.33975723e-001 -1.54224241e+000 + <_> + -1.84198833e+000 4.68461394e-001 1.42443144e+000 -2.70365715e+000 + 1.65155220e+000 -2.44623923e+000 + <_> + -1.06688821e+000 1.30729961e+000 2.79200792e+000 -1.70301580e+000 + 1.56478667e+000 -9.43165720e-001 + <_> + -1.34356701e+000 1.91723049e+000 -6.52762115e-001 -2.99274057e-001 + 2.87508678e+000 -2.51026273e+000 + <_> + -1.95435786e+000 3.54804128e-001 -1.43369138e+000 -1.80080736e+000 + 7.64794469e-001 -1.73110282e+000 + <_> + -9.35182944e-002 4.51459102e-002 1.49539316e+000 -3.33725214e+000 + 9.80689585e-001 -1.17926705e+000 + <_> + -1.30974799e-001 1.39090434e-001 2.61627316e-001 3.92385907e-002 + -2.41004035e-001 7.24722147e-001 + <_> + -2.38849565e-001 7.99098760e-002 1.06933296e+000 3.66622955e-001 + -3.67715120e-001 7.87341416e-001 + <_> + -2.35353634e-001 4.32813540e-002 5.12419462e-001 2.67273128e-001 + -3.22442979e-001 8.53517473e-001 + <_> + -1.81058422e-002 6.40262887e-002 1.76696241e-001 -7.73059204e-002 + 1.59137234e-001 6.78885698e-001 + <_> + -3.63704622e-001 7.07875416e-002 -2.83660024e-001 -8.53114426e-002 + 6.04614377e-001 5.63254237e-001 + <_> + 2.37597302e-001 4.70956899e-002 2.11097762e-001 2.47592162e-002 + 2.30080426e-001 2.96778628e-003 + <_> + 2.20617011e-001 7.49819204e-002 1.75510168e-001 2.03513846e-001 + -3.15919310e-001 5.27144670e-001 + <_> + -3.14761430e-001 1.31637573e-001 5.14219642e-001 2.70426333e-001 + -6.86736822e-001 8.18759084e-001 + <_> + 7.48004794e-001 1.38328761e-001 3.84262472e-001 -2.08511041e-003 + 2.06688136e-001 6.46182358e-001 + <_> + 1.72986484e+000 -1.09176427e-001 1.41790807e+000 -5.25383651e-001 + -5.34881473e-001 8.54539778e-003 + <_> + 1.74235034e+000 -9.50765908e-002 1.71013117e+000 -1.11982822e+000 + -2.15850789e-002 -2.67935216e-001 + <_> + 1.27239597e+000 -7.37022534e-002 5.62000155e-001 -8.83138001e-001 + 2.21280083e-001 -3.80497396e-001 + <_> + 1.55307019e+000 -7.38622844e-002 9.64027762e-001 -6.55821502e-001 + -3.86009812e-001 1.43034384e-001 + <_> + 1.63797152e+000 -2.01898515e-002 1.13875020e+000 -8.08658183e-001 + -4.87948954e-001 1.96968690e-001 + <_> + 1.54657769e+000 -1.19370714e-001 1.63568246e+000 -7.67518878e-001 + -1.32192239e-001 -4.81460281e-002 + <_> + 1.12207055e+000 6.92979842e-002 -1.83299407e-001 -1.22151077e+000 + 9.95345786e-002 -4.25901353e-001 + <_> + 1.18599629e+000 -8.29902962e-002 -1.60903618e-001 -5.43049455e-001 + 2.46468425e-001 -3.38647276e-001 + <_> + 1.76032948e+000 2.49164328e-002 6.91804469e-001 -1.45491052e+000 + 6.10150576e-001 -1.09723997e+000 + <_> + 1.73535848e+000 -4.42308150e-002 1.19112730e+000 -1.52685809e+000 + 1.00936449e+000 -1.27832532e+000 + <_> + 1.68242002e+000 -3.43278833e-002 5.73080778e-001 -1.31076694e+000 + -1.27261013e-001 -2.49105692e-001 + <_> + 1.72137475e+000 -7.09614456e-002 1.31336975e+000 -7.82363176e-001 + -4.00158048e-001 -4.11506034e-002 + <_> + 3.47469747e-001 -7.04554617e-002 2.56452989e-002 -3.25809062e-001 + 9.92372096e-001 -1.01023805e+000 + <_> + -6.80337012e-001 -7.70140663e-002 -1.50996935e+000 + -3.72285426e-001 8.41308177e-001 -9.26663876e-001 + <_> + 2.17121065e-001 -4.71018493e-001 -2.97152311e-001 -1.25832164e+000 + 4.00791407e-001 -1.74148947e-001 + <_> + 4.00408298e-001 -3.65810901e-001 -7.82824755e-001 1.07338451e-001 + -9.14967060e-001 1.09960032e+000 + <_> + -3.39233041e-001 7.88008980e-003 -9.20699656e-001 1.22728057e-001 + -1.60928428e-001 1.03146780e+000 + <_> + -4.73577052e-001 5.96065372e-002 -6.19841874e-001 -4.12490629e-002 + 3.69136304e-001 1.35369718e-001 + <_> + -1.84348667e+000 -4.38921481e-001 8.39012504e-001 5.93710780e-001 + -7.86585152e-001 1.93879008e-001 + <_> + -1.71763277e+000 -4.70046908e-001 1.44821632e+000 1.21595609e+000 + -1.17272723e+000 2.75893784e+000 + <_> + -1.96434629e+000 -1.45296645e+000 2.01866961e+000 4.68161196e-001 + 3.46620035e+000 3.80479741e+000 + <_> + -1.72012985e+000 -1.58037853e+000 2.05156612e+000 5.33459187e-001 + -3.36330473e-001 3.16094518e+000 + <_> + -2.13265109e+000 -4.05307531e+000 1.67142820e+000 -3.20536643e-001 + 6.22153759e-001 2.35978293e+000 + <_> + -1.84348667e+000 -8.16397727e-001 2.36871004e+000 -7.84417152e-001 + 3.80748081e+000 2.98396468e+000 + <_> + -1.72812057e+000 -1.01642489e+000 1.41221750e+000 -7.22129524e-001 + 1.81823421e+000 2.47358751e+000 + <_> + -1.83449709e+000 -1.44117999e+000 1.43450785e+000 -7.40029156e-001 + -1.16167709e-001 2.35558248e+000 + <_> + -1.93787706e+000 -1.45540261e+000 1.59221661e+000 -7.79564798e-001 + 3.73959231e+000 3.45915914e+000 + <_> + -2.13364983e+000 -4.45003653e+000 1.79330945e+000 -6.65163815e-001 + 1.93216646e+000 3.24452615e+000 + <_> + -2.07421899e+000 -4.29208517e+000 2.37112164e+000 7.62062147e-002 + 5.30943394e+000 3.92100263e+000 + <_> + -1.76807415e+000 -3.59167171e+000 2.82771945e+000 -1.11552730e-001 + -9.05911922e-001 1.53945744e+000 + <_> + 1.25227749e-001 1.05269000e-001 1.95826352e-001 -4.85820979e-001 + 1.76360235e-002 -4.81395274e-002 + <_> + 7.32880533e-002 1.56321476e-005 9.03627396e-001 2.59318464e-002 + 3.17342669e-001 -3.44692171e-001 + <_> + -2.55829871e-001 5.26748300e-002 -7.97073841e-002 -4.46882159e-001 + -4.69581746e-002 4.58014160e-002 + <_> + -4.62149642e-003 4.94564474e-002 5.25229096e-001 -4.33816344e-001 + -1.25460732e+000 8.25213075e-001 + <_> + -2.67316520e-001 1.10776804e-001 -7.22656369e-001 -3.24088424e-001 + 1.41766369e-001 -1.29687607e-001 + <_> + 5.02290010e-001 8.57334912e-001 -1.20190549e+000 1.10253549e+000 + 4.10436720e-001 1.92482192e-002 + <_> + -2.31000427e-002 7.58304596e-001 -3.93891424e-001 1.08793795e+000 + 5.71463943e-001 -2.45895192e-001 + <_> + -1.96534514e+000 2.93623596e-001 -1.90803707e-002 -2.48043942e+000 + -3.17271650e-002 7.60997906e-002 + <_> + -8.82102728e-001 9.16817226e-004 1.28402889e+000 -2.63652086e+000 + -1.29850194e-001 5.69108389e-002 + <_> + -2.01035216e-002 -2.15374544e-001 1.23413193e+000 -1.11705828e+000 + 7.45109081e-001 -5.40157378e-001 + <_> + -1.15978038e+000 -1.47515461e-001 8.88474286e-002 -8.40768456e-001 + -5.26714981e-001 3.55276406e-001 + <_> + -1.18774796e+000 -1.74426019e-001 1.30963728e-001 -1.51792407e+000 + 3.57395023e-001 -2.48151019e-001 + <_> + -5.85447133e-001 4.70914334e-001 -1.69225895e+000 4.97533977e-002 + -1.20868182e+000 2.15755865e-001 + <_> + -1.73860836e+000 -1.09016860e+000 1.46140277e+000 -1.22702718e+000 + -5.89408398e-001 5.22677660e-001 + <_> + -1.71563506e+000 -1.77853376e-001 7.35784233e-001 -1.52785015e+000 + 7.11703122e-001 -1.48292422e-001 + <_> + -1.66769075e+000 -2.09795356e+000 1.49778378e+000 -1.68421721e+000 + 9.08875883e-001 1.67011786e+000 + <_> + -1.33807337e+000 -1.58418155e+000 1.88911819e+000 -1.64672005e+000 + 6.07244194e-001 -7.68265247e-001 + <_> + -1.74510086e+000 -1.80156887e+000 1.43156898e+000 2.89450675e-001 + 1.57630360e+000 2.06988502e+000 + <_> + -1.18724847e+000 -1.70985508e+000 2.81102717e-001 -2.72981137e-001 + 3.67343158e-001 -1.31502256e-001 + <_> + 2.09130347e-001 -3.87971923e-002 9.42435861e-001 -9.48117495e-001 + 1.64727896e-001 7.18975723e-001 + <_> + -1.19473982e+000 1.05093944e+000 -2.38188431e-001 -1.23017156e+000 + 1.28571749e+000 1.99646187e+000 + <_> + -1.21571541e+000 1.20381141e+000 2.19516325e+000 -4.82478172e-001 + 1.53564334e+000 1.80476451e+000 + <_> + -6.10418141e-001 2.04397225e+000 7.29231834e-001 -1.71458077e+000 + 1.40660298e+000 5.61863303e-001 + <_> + -1.95285964e+000 -7.85199046e-001 1.81745803e+000 -1.73999643e+000 + 1.04485273e+000 -8.89422417e-001 + <_> + -1.09935057e+000 1.22015393e+000 2.88805914e+000 -1.76720107e+000 + 1.00746477e+000 -1.03512061e+000 + <_> + -1.67717969e+000 1.85930562e+000 1.00522149e+000 -1.20343792e+000 + 2.37894726e+000 2.04203796e+000 + <_> + -1.76208115e+000 1.54799807e+000 1.15252185e+000 3.51437420e-001 + 3.62137532e+000 2.57604194e+000 + <_> + -8.41150284e-001 2.68655896e-001 1.26643762e-001 6.35934770e-001 + -3.79337996e-001 7.75903165e-001 + <_> + -1.70764434e+000 2.08338809e+000 1.23981714e+000 -1.79787207e+000 + 4.07662898e-001 5.56983761e-002 + <_> + -1.68930739e-001 4.51825261e-002 2.15085626e-001 -1.10074234e+000 + -4.70825136e-001 7.67386913e-001 + <_> + -4.02659386e-001 5.04245222e-001 -7.98876345e-001 6.80844299e-003 + -1.00597155e+000 2.26863191e-001 + <_> + -1.86945653e+000 -2.35736281e-001 1.01351619e+000 -1.28583372e+000 + 1.51358634e-001 1.77354235e-002 + <_> + -2.06772637e+000 -8.51654485e-002 -2.49255866e-001 + -1.09591222e+000 -3.70720118e-001 1.34678340e+000 + <_> + -1.96484578e+000 8.01295638e-002 1.70288786e-001 -1.55262542e+000 + 1.51419616e+000 4.48166251e-001 + <_> + -1.72162807e+000 1.01362598e+000 -1.87566638e-001 7.25926280e-001 + 9.13922727e-001 -5.61407447e-001 + <_> + -7.05449656e-002 2.01280072e-001 -9.45048511e-001 -3.56086344e-001 + -3.01919609e-001 8.24301183e-001 + <_> + -3.27246934e-001 3.37504208e-001 -1.98606098e+000 -2.69831944e+000 + 1.12096536e+000 -9.28835988e-001 + <_> + -1.83000231e+000 3.07556152e-001 -9.07378733e-001 6.54588342e-001 + -1.10740416e-001 1.07069898e+000 + <_> + -1.87694776e+000 1.49617307e-002 -1.72733259e+000 1.79077988e-003 + -1.37011856e-001 -6.01068251e-002 + <_> + -2.10718060e+000 -4.11007136e-001 -2.73695874e+000 + -1.23514760e+000 2.47888546e-002 1.85129559e+000 + <_> + -2.11766839e+000 1.83713749e-001 -2.42579269e+000 -1.72741032e+000 + 2.57739097e-001 -1.95022784e-002 + <_> + -1.75508928e+000 7.06888795e-001 -2.66919422e+000 -1.51019239e+000 + 1.10808706e+000 -1.01388144e+000 + <_> + -1.74959564e+000 2.03470692e-001 -1.10176783e-002 1.21588242e+000 + -4.75847661e-001 8.21585953e-001 + <_> + -1.10534358e+000 3.99391800e-001 1.05985761e+000 9.80621219e-001 + -4.47703838e-001 8.31131458e-001 + <_> + 1.73671514e-001 -3.00766498e-001 2.14897728e+000 -9.04217422e-001 + 1.82445869e-001 -2.63769865e-001 + <_> + 2.33460348e-002 -2.93247670e-001 1.70196044e+000 -1.58305812e+000 + 1.16473532e+000 -9.72012043e-001 + <_> + 1.48558915e-002 -9.36880410e-002 8.53184819e-001 -8.01888168e-001 + 1.55894369e-001 3.70194376e-001 + <_> + -1.73860836e+000 -9.31766808e-001 7.13654697e-001 2.41468906e-001 + -2.13174462e-001 -2.56441355e-001 + <_> + -1.88493848e+000 -1.10753107e+000 1.15665957e-001 6.18464589e-001 + 8.39851201e-001 -1.08581197e+000 + <_> + -1.75908458e+000 -8.65618959e-002 5.98412342e-002 3.87527674e-001 + 1.06453729e+000 -1.08428693e+000 + <_> + -1.73311472e+000 -1.51028466e+000 -1.10449958e+000 4.84250277e-001 + 1.46432519e-001 2.42350578e-001 + <_> + -1.40149975e+000 -9.44575310e-001 -6.23497605e-001 + -3.16230319e-002 -7.20050156e-001 1.81651151e+000 + <_> + -2.07371950e+000 -3.26264173e-001 6.12896085e-001 3.81398886e-001 + -1.49399686e+000 1.67245710e+000 + <_> + -1.41498411e+000 -6.75931573e-001 -1.50178850e-001 1.41791970e-001 + -4.92268085e-001 9.30429816e-001 + <_> + -9.91975188e-001 -9.70508277e-001 7.20154345e-001 -7.38450646e-001 + -7.87563086e-001 1.95606738e-001 + <_> + -2.05723858e+000 -5.60462773e-001 1.22791421e+000 -1.42682791e+000 + 7.39585757e-001 -9.41813171e-001 + <_> + -2.09519458e+000 -4.40787911e-001 1.10717154e+000 -9.44836080e-001 + 9.71545577e-002 -3.06213796e-001 + <_> + -2.02377725e+000 -2.76894093e-001 1.46438384e+000 -8.65823925e-001 + -7.38807917e-001 9.85231936e-001 + <_> + -2.09669280e+000 1.51994163e-002 -1.09522700e+000 -2.04005647e+000 + 1.27202764e-001 -2.17865422e-001 + <_> + -1.84098959e+000 2.30062127e-001 5.27190089e-001 -1.35058022e+000 + 1.11018133e+000 1.95713449e+000 + <_> + -8.93589437e-001 7.94706821e-001 -1.38503850e+000 1.01316237e+000 + -4.72846441e-003 7.14733064e-001 + <_> + -1.74310315e+000 -3.64212841e-002 9.09539342e-001 3.14216278e-002 + -3.09866101e-001 -1.37260944e-001 + <_> + -1.77206957e+000 1.57544892e-002 8.50435793e-001 -6.76372051e-002 + -3.72694671e-001 -1.32412791e-001 + <_> + -1.22984082e-001 -1.55554160e-001 2.69900370e+000 -5.69207072e-002 + -5.12005687e-001 1.91600397e-001 + <_> + -3.50861289e-002 -1.69850420e-002 -7.51061559e-001 + -2.81999893e-002 -8.04414809e-001 5.66591442e-001 + <_> + 2.40094393e-001 8.09852034e-003 -2.34835789e-001 -1.35776430e-001 + 3.83116379e-002 -1.97842553e-001 + <_> + -1.83699417e+000 -6.97772801e-002 6.30666137e-001 -1.78274855e-001 + -5.66976517e-002 -1.24081440e-001 + <_> + -1.62573946e+000 -9.64411572e-002 -7.02290058e-001 + -1.80954605e-001 -5.71344137e-001 9.05485302e-002 + <_> + -1.58578587e+000 -8.73721689e-002 -4.98624712e-001 + -1.42528370e-001 -5.54955542e-001 5.26037998e-002 + <_> + 9.61257219e-001 -2.73598999e-001 -7.87089348e-001 -5.36163628e-001 + 1.31442860e-001 -3.35135877e-001 + <_> + 4.50849712e-001 2.35114768e-001 -6.37981296e-002 -3.32223743e-001 + -5.39606631e-001 6.38452247e-002 + <_> + 5.35751164e-001 2.41724193e-001 1.40802905e-001 -3.13759804e-001 + -4.87836331e-001 2.43778974e-002 + <_> + 5.62220454e-001 2.47517973e-001 1.21056028e-001 -3.53826851e-001 + -5.67610264e-001 4.59370501e-002 + <_> + 5.98178685e-001 2.24041700e-001 2.52636850e-001 -3.38308156e-001 + -4.82197762e-001 -2.78051030e-002 + <_> + 6.76587701e-001 8.88200849e-002 4.13468957e-001 -7.43199646e-001 + 3.87784749e-001 -4.32484448e-001 + <_> + 2.93032944e-001 5.17752208e-002 -7.01517820e-001 -6.68332912e-003 + 5.43616712e-001 -7.09596097e-001 + <_> + 6.40129983e-001 2.65032127e-002 -3.03145945e-001 -2.33701736e-001 + 3.63451004e-001 -6.51055753e-001 + <_> + 3.70443076e-001 2.03964695e-001 -4.40433115e-001 -3.42612177e-001 + 1.74026400e-001 -3.69964182e-001 + <_> + 2.10628614e-001 2.22329617e-001 -6.00838244e-001 -4.15008128e-001 + 1.24917254e-001 -2.76743829e-001 + <_> + 1.41772723e+000 -1.41364992e-001 2.43522000e+000 -9.16404836e-003 + -3.81346792e-001 -1.37539906e-002 + <_> + 1.33182693e+000 -1.21238418e-001 2.27754021e+000 -1.23769632e-002 + -7.71014765e-002 -1.20641716e-001 + <_> + -1.66433647e-001 -1.50640100e-001 -4.16463822e-001 + -1.39060229e-001 4.61883157e-001 -5.02702415e-001 + <_> + 3.38338576e-002 -1.69707820e-001 -1.12401187e+000 -4.36275244e-001 + 2.51154393e-001 -2.24698603e-001 + <_> + -2.40988843e-002 -2.10231319e-001 -9.49531913e-001 + -4.68850225e-001 1.12762488e-001 -1.03261791e-001 + <_> + -4.54599082e-001 -1.69268757e-001 -6.05324656e-002 + -1.78298756e-001 8.33233297e-001 -4.22459960e-001 + <_> + -6.45877004e-001 -1.74506038e-001 -2.87460744e-001 + -1.89742997e-001 8.45989287e-001 -3.60654175e-001 + <_> + 2.81546265e-001 -9.83664691e-002 -1.40513629e-001 -3.67448032e-001 + 3.21719766e-001 -4.64227349e-001 + <_> + 1.86157018e-001 -2.75693804e-001 -2.87711143e-001 -1.28752306e-001 + 6.06581807e-001 -2.32153177e-001 + <_> + 3.55460465e-001 8.93990755e-001 6.56372011e-002 -2.12511063e-001 + -7.85835028e-001 4.41812575e-001 + <_> + -9.55018103e-001 1.05934584e+000 -2.29862928e+000 1.36046290e+000 + -1.37897420e+000 1.25707066e+000 + <_> + -5.84448338e-001 1.21319562e-001 -7.15647936e-001 2.38046989e-001 + 4.83449936e-001 -2.91590154e-001 + <_> + -3.80185485e-001 3.02390516e-001 2.41548330e-001 2.25221470e-001 + 2.82359314e+000 -2.44829249e+000 + <_> + 9.55264151e-001 4.93420601e-001 1.36028838e+000 -1.42541811e-001 + 1.64390314e+000 -1.45723450e+000 + <_> + 9.50769365e-001 5.01551807e-001 1.29920006e+000 -1.39603168e-001 + 1.67611170e+000 -1.46512890e+000 + <_> + 8.54880691e-001 5.04172981e-001 1.02019775e+000 -1.39979348e-001 + 1.70199776e+000 -1.49812412e+000 + <_> + 9.62256014e-001 5.03998101e-001 1.30163288e+000 -1.22227296e-001 + 1.65242493e+000 -1.47280300e+000 + <_> + -1.78919151e-001 2.80883282e-001 -1.65862978e+000 -4.01538759e-001 + 5.58181167e-001 -3.15327674e-001 + <_> + -1.18489303e-001 2.65838057e-001 -1.51338530e+000 -4.45478290e-001 + 6.68063819e-001 -4.21117425e-001 + <_> + -6.30536601e-002 2.92168140e-001 -1.42336988e+000 -4.32525158e-001 + 4.38987076e-001 -2.31704593e-001 + <_> + -1.62438273e-001 -1.71355680e-001 3.50446075e-001 1.88562438e-001 + -1.76788777e-001 1.10540405e-001 + <_> + -1.54447556e-001 -1.51222154e-001 -3.16234976e-001 1.08711191e-001 + 2.30557036e+000 -1.57949269e+000 + <_> + -2.60824054e-001 -2.34480619e-001 -3.08960378e-001 + -1.54764518e-001 2.04847407e+000 -1.39086902e+000 + <_> + -1.91404656e-001 -1.98894069e-001 -3.48351330e-001 1.09811373e-001 + 2.42083502e+000 -1.48043895e+000 + <_> + -3.02775353e-001 -2.14472100e-001 -1.58760771e-001 5.73348403e-001 + 1.42547452e+000 -9.57756102e-001 + <_> + -3.47723186e-001 -2.08207056e-001 -3.31959724e-001 4.47805941e-001 + 1.75439799e+000 -1.15032840e+000 + <_> + -2.47339711e-001 -2.02366859e-001 -7.96309114e-001 4.67462450e-001 + 8.99863362e-001 -4.66320127e-001 + <_> + -2.22368702e-001 -1.68700114e-001 3.54791991e-002 2.90762514e-001 + 8.13806832e-001 -4.32133108e-001 + <_> + -3.00777674e-001 -2.39219815e-001 -9.26277190e-002 5.85194230e-001 + 1.00810027e+000 -4.70945179e-001 + <_> + 2.15123385e-001 -2.14953467e-001 -1.09075689e+000 1.19861174e+000 + 3.48943591e-001 -4.61810715e-002 + <_> + 1.24228917e-001 -1.76160216e-001 -1.25324154e+000 1.12175024e+000 + 4.68764037e-001 -1.49281383e-001 + <_> + 1.37713253e-001 -1.94459796e-001 -1.01266026e+000 1.15626597e+000 + 3.18177432e-001 -2.56503001e-002 + <_> + -3.36735934e-001 -1.86197937e-001 1.62186563e-001 6.53748870e-001 + 1.16477287e+000 -7.73143649e-001 + <_> + -1.03442597e+000 5.20786643e-001 1.69082189e+000 -2.19172931e+000 + -1.05951631e+000 1.33105621e-001 + <_> + -5.92439055e-001 -1.67526826e-001 5.66834092e-001 1.56412035e-001 + -3.62163246e-001 -1.02026556e-002 + <_> + -8.08188558e-001 -1.63055301e-001 4.82013702e-001 -1.14153042e-001 + -3.10262293e-001 1.38457641e-001 + <_> + -8.93589437e-001 -1.02966934e-001 5.58138415e-002 2.08586171e-001 + -9.51887965e-002 -4.70018804e-001 + <_> + -1.07288122e+000 -1.45783171e-001 -9.04716924e-002 8.86353254e-002 + -1.26045942e-001 -4.62883174e-001 + <_> + -9.56016958e-001 -1.14001162e-001 -3.47012371e-001 1.57980472e-001 + -1.49026573e-001 -5.12088180e-001 + <_> + -9.30047095e-001 -4.65091877e-002 -1.70002148e-001 + -1.74353465e-001 -6.54719412e-001 1.55496716e-001 + <_> + -1.33971334e-001 -2.79711355e-002 9.21948373e-001 9.44767892e-002 + -2.90554553e-001 5.64167798e-002 + <_> + 1.37154618e-003 5.40755987e-001 5.95697537e-002 3.82313937e-001 + -8.41771007e-001 6.34292364e-002 + <_> + 1.07812154e+000 -1.17832541e-001 6.39891207e-001 2.32192963e-001 + -6.87260985e-001 3.85078698e-001 + <_> + 1.02518296e+000 -1.10833444e-001 7.84990668e-001 2.65465885e-001 + -6.04936838e-001 3.35017890e-001 + <_> + 1.03766847e+000 -8.70270059e-002 7.63493657e-001 2.80647874e-001 + -6.68619990e-001 3.27901274e-001 + <_> + -3.99662882e-001 5.53953368e-003 -5.59085667e-001 -1.94115937e-001 + 2.20611066e-001 -6.93508923e-001 + <_> + -2.46840298e-001 2.95906011e-002 -7.25112855e-001 -1.68149322e-001 + 7.11918846e-002 -5.72118938e-001 + <_> + -3.43228400e-001 -3.96094564e-003 -7.13186145e-001 + -1.70614198e-001 3.54153961e-001 -7.29510367e-001 + <_> + 5.31755805e-001 1.50734717e-002 2.76811749e-001 -1.58395842e-001 + -1.83698639e-001 4.12096739e-001 + <_> + 5.79700172e-001 3.22052985e-002 3.20248544e-001 -1.38837755e-001 + -2.64406055e-001 3.91107917e-001 + <_> + 1.28582111e-002 -2.85738725e-002 -7.54200757e-001 -1.29625097e-001 + -2.40733996e-001 2.64815360e-001 + <_> + 1.54008520e+000 -3.43610823e-001 -2.38510370e-002 -5.08541226e-001 + -7.69217014e-002 6.64161086e-001 + <_> + 1.47965539e+000 -4.29095238e-001 -2.60961115e-001 -4.29065138e-001 + -7.60187581e-002 8.58252883e-001 + <_> + -3.26248109e-001 3.39862071e-002 -3.26248109e-001 -3.53208095e-001 + 1.37527317e-001 -2.77605224e-002 + <_> + -3.77188951e-001 -5.84155619e-002 -3.66076589e-001 + -2.69139767e-001 1.34126782e-001 2.85826679e-002 + <_> + -2.59825230e-001 -6.00804687e-002 -2.44316503e-001 + -2.79465765e-001 2.58192033e-001 1.84880346e-002 + <_> + 5.87191463e-001 -3.09208274e-001 3.56716484e-001 -3.40340674e-001 + -3.13598633e-001 1.32647946e-001 + <_> + -7.60385841e-002 5.47623932e-001 2.11591482e+000 6.78747952e-001 + 5.51636934e-001 -3.77645016e-001 + <_> + -7.35772610e-001 7.22229704e-002 -1.26428291e-001 4.87534255e-001 + 4.95040119e-001 1.04475871e-001 + <_> + 1.14804029e+000 2.08242878e-001 -7.74269164e-001 2.76060432e-001 + 1.54838100e-001 2.93410301e-001 + <_> + 9.85728800e-001 1.80164412e-001 -6.46473229e-001 9.73695517e-002 + -3.13821256e-001 2.93730497e-001 + <_> + -4.49604899e-001 1.01128757e-001 -4.86216098e-002 1.33194834e-001 + -2.37240404e-001 -1.68864220e-001 + <_> + -4.86561984e-001 7.61457309e-002 -3.26965414e-002 1.59127474e-001 + -1.22381367e-001 -1.91775963e-001 + <_> + -4.55098510e-001 -5.33695593e-002 3.18129025e-002 -8.49616453e-002 + -3.07623386e-001 3.71607482e-001 + <_> + -4.34622288e-001 -1.71056222e-002 3.56584303e-002 3.25031251e-001 + -4.38188225e-001 4.85774100e-001 + <_> + -4.83565480e-001 -2.32879724e-002 -4.68929484e-003 3.22120786e-001 + -4.29229558e-001 4.92538512e-001 + <_> + -5.47990620e-001 2.55130138e-002 -2.43739277e-001 -1.77567482e-001 + -3.31257194e-001 2.01725215e-001 + <_> + -4.20139104e-001 5.43919727e-002 3.50601189e-002 5.94678037e-002 + -2.27434158e-001 1.72372118e-001 + <_> + -5.75958192e-001 4.25894484e-002 1.08272359e-002 6.17716536e-002 + -2.03941941e-001 1.59818470e-001 + <_> + -1.26480028e-001 1.21559044e-002 4.71283674e-001 4.73294795e-001 + 3.95428210e-001 -1.70970380e-001 + <_> + -1.17676067e+000 -3.08443531e-002 -1.63679048e-001 1.23981096e-001 + -6.28849268e-001 3.65880758e-001 + <_> + -1.24767828e+000 1.12291205e+000 2.48644543e+000 -1.41044974e+000 + 2.56409931e+000 -2.23611736e+000 + <_> + -1.30960643e+000 8.15511405e-001 2.40068150e+000 2.69425720e-001 + -2.46343374e+000 1.54474032e+000 + <_> + -1.28613365e+000 6.49353087e-001 1.97766817e+000 3.66498023e-001 + -2.40577817e+000 1.65019000e+000 + <_> + -1.85047853e+000 -8.14321876e-001 -1.98708892e+000 3.41542453e-001 + 1.12898707e+000 -8.94163966e-001 + <_> + -4.25774306e-002 4.32182938e-001 -3.27671409e-001 8.33324671e-001 + -3.75208795e-001 8.57848674e-002 + <_> + 1.33576309e-002 5.17054379e-001 -1.07158490e-001 1.24809527e+000 + -3.02935481e-001 2.18070045e-001 + <_> + -7.55391642e-002 1.03360927e+000 3.08591795e+000 -2.49206387e-002 + 1.24078166e+000 -1.49637437e+000 + <_> + -9.52021599e-001 2.12488127e+000 2.61529040e+000 -6.74581826e-001 + 3.33080649e+000 -2.72720146e+000 + <_> + -1.51786470e+000 1.33225656e+000 1.66391897e+000 -2.23680496e-001 + 3.05421972e+000 -2.44206214e+000 + <_> + -1.65434808e-001 1.76822454e-001 1.44511372e-001 2.50120163e-001 + -2.85829455e-001 2.06048608e-001 + <_> + -1.29476547e-001 1.78459108e-001 4.88813877e-001 2.56830394e-001 + -2.02440158e-001 1.98869213e-001 + <_> + -1.15492783e-001 -1.92493290e-001 3.30032349e-001 -9.26220059e-001 + 1.02788165e-001 -2.15975061e-001 + <_> + -2.48837978e-001 -1.34517565e-001 -3.59911621e-001 + -5.09240031e-001 -9.18775797e-001 5.48875511e-001 + <_> + -1.80916831e-001 -5.72066195e-002 -2.71195650e-001 + -6.80016577e-001 -6.13776863e-001 2.80073613e-001 + <_> + -2.39848405e-001 -1.17725849e-001 -2.38763019e-001 + -7.52736568e-001 -8.11497033e-001 4.10737038e-001 + <_> + 1.11243993e-001 1.61553457e-001 -3.82560432e-001 2.81068981e-001 + 2.66390264e-001 -4.09414977e-001 + <_> + -1.69929579e-001 2.42846966e-001 -1.61438674e-001 -2.73100048e-001 + -3.66816252e-001 -3.29600483e-001 + <_> + -9.30188745e-002 -2.09060647e-002 1.99573990e-002 -2.47387111e-001 + -2.40126416e-001 -1.11607146e-002 + <_> + -8.05333629e-002 -2.81581581e-002 6.53630868e-002 -2.43228152e-001 + -2.57581294e-001 9.77892522e-003 + <_> + -1.96041018e-002 -3.03686671e-002 2.13530540e-001 -2.44335443e-001 + -2.31856272e-001 -7.45988637e-002 + <_> + 8.21419537e-001 1.52531505e-001 -1.47647452e+000 9.90478396e-002 + -5.90878606e-001 9.83643830e-002 + <_> + 1.25727177e-001 2.42755428e-001 -1.13590956e+000 1.07005514e-001 + -4.62561548e-001 4.00031768e-002 + <_> + 4.03263234e-002 2.45885104e-001 -1.65691125e+000 1.35637015e-001 + -5.71539044e-001 8.65796432e-002 + <_> + 7.33022153e-001 6.41055182e-002 4.87675250e-001 -4.79766786e-001 + 3.94625425e-001 -2.99098074e-001 + <_> + 8.37900400e-001 2.86171306e-002 9.44684148e-001 -4.55572188e-001 + 3.40898186e-001 -2.73456693e-001 + <_> + 1.74170941e-001 3.68602216e-001 -2.08476377e+000 -3.06999564e-001 + 7.53462315e-001 -6.30761504e-001 + <_> + -2.97781140e-001 4.88518784e-003 -1.85076904e+000 -2.73418248e-001 + 3.96297127e-001 -3.61406595e-001 + <_> + -4.60733734e-002 -1.63291190e-002 -9.36925232e-001 + -3.53486091e-001 2.57421643e-001 -2.73473442e-001 + <_> + 2.19618171e-001 -1.01567969e-001 -1.77113318e+000 -2.94871092e-001 + -8.24472830e-002 -2.17999533e-001 + <_> + -1.26979440e-001 2.47295666e-002 -2.70750189e+000 9.26842839e-002 + -3.01686049e-001 2.52144098e-001 + <_> + -2.55971439e-002 -1.69740862e-003 -2.56817722e+000 1.23886824e-001 + -3.43946427e-001 2.63332248e-001 + <_> + -5.70606180e-002 -4.08205748e-001 9.63861704e-001 -7.20917404e-001 + 1.52663309e-002 -4.45654690e-001 + <_> + -2.41346672e-001 -6.29931390e-002 3.64101768e-001 -7.83864185e-002 + 1.23786367e-001 -6.37762070e-001 + <_> + -2.45983042e-002 -2.75991946e-001 1.25490141e+000 -6.94120228e-001 + -3.42608541e-001 -4.26066875e-001 + <_> + -3.38733613e-001 -2.70153493e-001 7.25423217e-001 -8.78766716e-001 + 3.80670488e-001 -1.11115110e+000 + <_> + -4.61590976e-001 -2.49877781e-001 3.39352280e-001 -9.05837536e-001 + 5.92444181e-001 -1.25738895e+000 + <_> + -3.21753323e-001 -1.05225265e-001 5.22709370e-001 -4.45714325e-001 + 7.18038857e-001 -8.32047462e-001 + <_> + -2.34854206e-001 -3.16181481e-001 8.87939572e-001 -8.60470414e-001 + -1.81437850e-001 -6.15268826e-001 + <_> + -6.16910636e-001 3.79383594e-001 -1.46928692e+000 4.57027435e-001 + 5.32635391e-001 -7.03564048e-001 + <_> + -6.73345089e-001 3.34953755e-001 -1.40017283e+000 3.48043501e-001 + 7.59075820e-001 -9.26419258e-001 + <_> + -5.45493543e-001 -3.30119319e-002 -5.28532326e-001 3.44696671e-001 + -2.47826576e-001 -2.66397715e-001 + <_> + -4.89059091e-001 -4.52163396e-004 -6.70824766e-001 2.68191546e-001 + 5.68992913e-001 -7.34865367e-001 + <_> + -7.45261610e-001 1.24652326e+000 3.32733226e+000 2.33276531e-001 + 1.17742085e+000 -8.64297211e-001 + <_> + -1.35954845e+000 1.25106287e+000 -1.45916831e+000 1.08716853e-001 + 2.53883982e+000 -2.02257156e+000 + <_> + -1.57479858e+000 1.24273670e+000 -2.83930421e+000 9.28413391e-001 + 2.13278055e+000 -7.32416093e-001 + <_> + -1.15828216e+000 1.39995527e+000 1.45453143e+000 1.55031240e+000 + 2.41588855e+000 -1.90648186e+000 + <_> + -1.22170854e+000 1.52056420e+000 8.15167129e-001 1.44640422e+000 + 2.37399268e+000 -1.88880765e+000 + <_> + -1.28263772e+000 1.12390399e+000 1.54874289e+000 1.37196672e+000 + 9.55768526e-001 -2.33138040e-001 + <_> + -4.47107792e-001 2.71502994e-002 1.41156092e-001 -9.36809853e-002 + -3.83483797e-001 5.66981316e-001 + <_> + 1.43620586e+000 1.74804181e-001 1.57806802e+000 -5.57239413e-001 + 3.16333503e-001 -1.26511192e+000 + <_> + 2.01139629e-001 4.05413479e-001 -7.34316826e-001 2.24302396e-001 + 3.65781367e-001 -8.37002277e-001 + <_> + 2.52080500e-001 4.15491611e-001 -7.40031064e-001 2.25739568e-001 + 4.41993266e-001 -9.08369720e-001 + <_> + 1.39874923e+000 -5.15126763e-003 4.32013959e-001 1.98008791e-001 + -2.79969871e-001 -2.19896972e-001 + <_> + 1.57754171e+000 2.03215018e-001 1.42728066e+000 -5.56850433e-001 + 3.89342934e-001 -1.18839455e+000 + <_> + 1.68341875e+000 1.71311170e-001 1.52802348e+000 -5.66635132e-001 + 5.53670287e-001 -1.29453814e+000 + <_> + 1.84573042e+000 4.47090492e-002 1.38596034e+000 -3.80072087e-001 + -1.55818611e-001 -1.28002560e+000 + <_> + 1.80477786e+000 1.33989407e-002 1.45677185e+000 -2.04605073e-001 + -3.39855067e-002 -9.99505401e-001 + <_> + 3.24496418e-001 2.89121479e-001 -1.06013227e+000 -8.91187727e-001 + -6.43395307e-003 -9.88725722e-001 + <_> + 1.41223359e+000 -1.34640408e-003 1.38636887e+000 -7.04296753e-002 + -4.55359578e-001 -5.61156332e-001 + <_> + 3.90419871e-001 -2.45751277e-001 4.63173129e-002 -2.91605890e-001 + 2.41587788e-001 -5.59674323e-001 + <_> + 2.87039906e-001 -2.64519602e-001 -1.31683946e-001 -3.21179658e-001 + 3.03822577e-001 -6.05701566e-001 + <_> + 2.80048013e-001 -9.33370367e-002 3.46698076e-001 -4.37051922e-001 + -9.61201936e-002 -2.15604350e-001 + <_> + 8.38399827e-001 -1.20400049e-001 9.72828448e-001 -5.32593071e-001 + -2.48882443e-001 -1.06722929e-001 + <_> + 1.37213841e-001 -1.84044078e-001 8.69212687e-001 -2.71662883e-002 + 3.68874341e-001 -2.23682910e-001 + <_> + 1.73172101e-001 -1.69062480e-001 -4.95135009e-001 -2.25047115e-002 + 3.79856467e-001 -4.00560826e-001 + <_> + 8.72718170e-002 -1.74573436e-001 -4.91756022e-001 -5.37925810e-002 + 3.02988052e-001 -3.45510513e-001 + <_> + 4.28234227e-002 -3.00990611e-001 -2.74756521e-001 8.86308476e-002 + 5.60621262e-001 -2.78097063e-001 + <_> + 9.42637026e-002 -2.93998748e-001 -2.76517183e-001 9.64055657e-002 + 5.61485171e-001 -3.00284147e-001 + <_> + -9.55159739e-002 -3.38010602e-002 3.37976068e-002 -1.14214279e-001 + 1.62893489e-001 -2.31670931e-001 + <_> + -1.25980601e-001 -3.11511662e-002 7.01311156e-002 -1.42659917e-001 + 1.86406255e-001 -2.21397862e-001 + <_> + 4.09897268e-001 8.20300460e-001 1.17700779e+000 1.07243367e-001 + -8.11442077e-001 8.33367050e-001 + <_> + -1.48953930e-001 1.66017383e-001 8.03385377e-001 2.50802308e-001 + 6.30612075e-001 -8.86610866e-001 + <_> + -1.20486982e-001 1.89878583e-001 1.09774613e+000 8.12623575e-002 + 9.01631415e-001 -1.16658580e+000 + <_> + -1.09999157e-001 2.25449204e-001 1.24467218e+000 1.45697832e-001 + 9.41942990e-001 -1.25324082e+000 + <_> + 9.62613821e-002 -2.54391376e-002 4.11278665e-001 -6.94277048e-001 + 5.57974219e-001 -9.80250716e-001 + <_> + -2.03890160e-001 5.87763131e-001 -1.64655805e+000 1.56440124e-001 + 3.07472236e-003 5.19911289e-001 + <_> + -2.56329268e-001 5.64525187e-001 -1.66385114e+000 1.73386320e-001 + 1.24232918e-001 3.64951521e-001 + <_> + 1.28224283e-001 6.03457689e-001 -1.75866616e+000 1.01549923e-001 + 2.15271503e-001 2.61198610e-001 + <_> + -5.05681559e-002 -8.11472088e-002 2.61651039e-001 4.93205637e-001 + 2.31801674e-001 -2.63483047e-001 + <_> + 6.36574812e-003 -7.82987699e-002 2.43493497e-001 4.01342154e-001 + 4.28377092e-001 -3.70559722e-001 + <_> + -1.00010753e-001 -5.83022460e-002 3.02354574e-001 4.00469244e-001 + 4.07847434e-001 -3.58449817e-001 + <_> + -1.98396534e-001 6.30079955e-002 3.17423046e-001 5.20063818e-001 + 1.39602572e-001 -7.18776211e-002 + <_> + -1.12995677e-001 2.14252412e-001 -3.45926464e-001 8.50734115e-001 + 7.88307965e-001 -9.14043009e-001 + <_> + 2.13625133e-001 -6.91556036e-002 -4.99486595e-001 1.43850103e-001 + 1.07677317e+000 -9.77945447e-001 + <_> + 2.11128026e-001 -9.86649171e-002 -6.09457076e-001 7.53901526e-002 + 1.05905080e+000 -8.37638855e-001 + <_> + -1.96398854e-001 6.82285577e-002 -2.54134595e-001 6.55438244e-001 + -3.76561046e-001 6.39867932e-002 + <_> + -6.20548204e-002 7.62552619e-002 1.94084138e-001 6.82452738e-001 + -2.57229775e-001 -2.68544420e-003 + <_> + -8.65264088e-002 4.50565815e-002 -9.62710559e-001 4.61461991e-001 + 6.77005589e-001 -8.41424644e-001 + <_> + 1.80663392e-001 -1.08244717e-002 -3.98287833e-001 4.84666795e-001 + 6.84718430e-001 -6.02831364e-001 + <_> + -2.22368702e-001 -2.76240762e-002 -2.78844535e-001 5.08274734e-001 + 3.27522099e-001 -2.72119761e-001 + <_> + -2.07386091e-001 -2.18403935e-002 -1.46712855e-001 5.11740148e-001 + 3.92298371e-001 -3.73759717e-001 + <_> + -2.77804345e-001 3.24446350e-001 -1.43851483e+000 2.78394550e-001 + 7.11191237e-001 -5.40659249e-001 + <_> + -2.04389572e-001 2.76400089e-001 -9.47913587e-001 1.01535094e+000 + 5.49236715e-001 -1.78485200e-001 + <_> + -2.36851886e-001 2.51052022e-001 -9.93134737e-001 1.06563044e+000 + 4.32749033e-001 -1.33065805e-001 + <_> + -1.91904068e-001 2.47883186e-001 -7.46609807e-001 1.00974512e+000 + 4.96600479e-001 -2.10363328e-001 + <_> + -1.87295246e+000 9.11099017e-001 9.21078563e-001 1.22961974e+000 + -3.87952089e-001 -4.00342673e-001 + <_> + -1.18874681e+000 1.88140452e+000 -2.09923124e+000 9.96012807e-001 + 2.81561576e-002 -1.16386020e+000 + <_> + -7.41266251e-001 -9.27928537e-002 7.50203952e-002 -1.13307372e-001 + -1.14206254e-001 -2.72530377e-001 + <_> + -2.47839138e-001 -1.78126723e-001 2.63395876e-001 -6.18678331e-002 + -1.46288928e-002 -1.60015784e-002 + <_> + -2.08384931e-001 -1.79785475e-001 3.55327427e-001 -1.08396620e-001 + -8.06042254e-002 6.11139536e-002 + <_> + -8.05691481e-001 -8.51755515e-002 -3.74692798e-001 + -7.82515109e-002 -6.98930956e-003 -4.90677297e-001 + <_> + -1.74759793e+000 -5.60700774e-001 -1.95624068e-001 4.06788021e-001 + -1.10548688e-002 -3.16547453e-002 + <_> + 5.63718736e-001 -6.96688518e-002 -1.00917840e+000 -3.49806130e-001 + -1.00109053e+000 7.66813219e-001 + <_> + 7.38016367e-001 -2.93037117e-001 -4.04903501e-001 -3.35760981e-001 + -1.13620746e+000 7.24947035e-001 + <_> + -3.03274781e-001 3.63341793e-002 -6.00205660e-001 -3.75764459e-001 + -4.75309461e-001 5.42881548e-001 + <_> + -3.47723186e-001 6.04045019e-002 -7.09667444e-001 -3.07081461e-001 + -6.18769467e-001 6.18106902e-001 + <_> + -2.43843764e-001 2.63017369e-003 -1.84954643e-001 -2.88064599e-001 + -1.51250169e-001 1.62025020e-001 + <_> + -1.33507693e+000 -2.04944476e-001 -1.69398010e+000 1.05862528e-001 + 2.37080365e-001 -6.08743578e-002 + <_> + -1.79953766e+000 -3.85835883e-003 6.99904084e-001 2.62175143e-001 + 3.62187654e-001 -4.75912571e-001 + <_> + 1.10808671e+000 -1.14975323e-003 6.04395457e-002 -1.12373829e-001 + -6.28564181e-003 2.44161770e-001 + <_> + 1.07162905e+000 -1.38173215e-002 -1.24230102e-001 2.62928456e-001 + 2.95112640e-001 1.85203537e-001 + <_> + 8.73359263e-001 2.72141486e-001 4.00972992e-001 2.66207427e-001 + 3.96156073e-001 2.14697182e-001 + <_> + 8.16425323e-001 2.77910471e-001 2.83854395e-001 2.91942477e-001 + 5.63381672e-001 1.07260019e-001 + <_> + 8.21918964e-001 2.91891307e-001 2.95248657e-001 3.58487785e-001 + 2.31988683e-001 1.72257036e-001 + <_> + -1.36853802e+000 -8.99314731e-002 1.52813911e-001 3.72418314e-002 + 6.93466783e-001 -8.01163435e-001 + <_> + -1.02293921e+000 -3.20458412e-001 -4.70040262e-001 1.92522407e-002 + 2.58222401e-001 3.33803147e-001 + <_> + 8.82706642e-002 1.95074365e-001 8.86368632e-001 1.66060016e-001 + -7.02819705e-001 8.15244555e-001 + <_> + 8.87700841e-002 1.80573657e-001 9.14731741e-001 1.77324742e-001 + -6.88289464e-001 8.32145274e-001 + <_> + -4.89558518e-001 -1.19169325e-001 -2.47431934e-001 9.11078602e-002 + 8.91700566e-001 -3.99157226e-001 + <_> + -5.49988329e-001 -1.15569636e-001 -4.81792957e-001 3.36828738e-001 + -9.04528052e-002 2.84875751e-001 + <_> + 7.77828395e-002 2.05752873e+000 1.25291646e-001 -1.50254190e+000 + 2.61915278e+000 -2.41730690e+000 + <_> + -6.93321943e-001 1.93487272e-001 2.78300691e+000 -3.51533443e-002 + 2.15511060e+000 -2.19575930e+000 + <_> + -1.72961879e+000 1.60016835e+000 1.47654510e+000 -7.45248675e-001 + -3.75650704e-001 -6.10963464e-001 + <_> + -1.76457822e+000 -7.91147828e-001 2.37567616e+000 -1.41650274e-001 + 2.16798052e-001 -4.40533608e-001 + <_> + -1.24981761e-001 -4.17286456e-002 -1.89316764e-001 5.15157461e-001 + 1.09264636e+000 -1.24152708e+000 + <_> + -2.01428843e+000 1.61885679e-001 -6.85811520e-001 3.15325201e-001 + 7.35119224e-001 -6.94761038e-001 + <_> + -2.00030446e+000 -2.23719907e+000 2.32906866e+000 9.68487740e-001 + 2.53228784e+000 -2.11457682e+000 + <_> + -1.01009592e-001 2.55284452e+000 2.46562052e+000 4.79462355e-001 + 1.18424416e+000 -9.20305848e-001 + <_> + -1.25417078e+000 1.16756213e+000 2.29806185e+000 9.67361391e-001 + 1.80992174e+000 -2.99342394e-001 + <_> + -1.05504379e-001 1.00860924e-001 -2.17424169e-001 4.73531941e-003 + 4.49564494e-002 -8.93626958e-002 + <_> + -1.06503218e-001 5.29854298e-002 3.02604586e-002 2.23011468e-002 + -3.64824295e-001 5.72683692e-001 + <_> + -8.65264088e-002 7.00959489e-002 1.15974337e-001 -3.64688342e-003 + -4.55437243e-001 6.49681151e-001 + <_> + -1.76422045e-001 7.05489144e-002 -3.08530748e-001 7.61289835e-001 + 4.16397601e-002 7.66299486e-001 + <_> + -2.28861168e-001 2.61102349e-001 -4.12391603e-001 5.06611943e-001 + 5.21209419e-001 5.22920966e-001 + <_> + -4.52102005e-001 4.02471602e-001 -4.18003082e-001 5.00505626e-001 + -1.11786805e-001 6.82947814e-001 + <_> + -7.65380040e-002 1.66031912e-001 -1.95800662e-001 7.00814486e-001 + -3.88340831e-001 6.85378969e-001 + <_> + -2.10382611e-001 1.78460047e-001 -4.98896211e-001 7.05837905e-001 + -4.58250433e-001 7.97886789e-001 + <_> + -1.33971334e-001 2.24035308e-002 3.37668061e-001 -1.15131259e+000 + -1.07607210e+000 -6.76817372e-002 + <_> + -1.39964372e-001 9.48959682e-003 4.48476702e-001 -1.18296409e+000 + -1.29141438e+000 9.98222008e-002 + <_> + -7.60385841e-002 3.39277238e-001 1.65288806e-001 -3.92530113e-001 + -1.09847045e+000 -7.54895201e-003 + <_> + -8.25310498e-002 3.49117994e-001 1.88364610e-001 -4.03766423e-001 + -1.20155072e+000 7.74439871e-002 + <_> + -5.95577173e-002 4.02652800e-001 1.85204372e-001 -4.10321593e-001 + -1.19321322e+000 9.60111395e-002 + <_> + -1.63622725e+000 9.33537856e-002 2.23295540e-002 5.21468878e-001 + 1.15968734e-001 4.86356467e-001 + <_> + -2.62381556e-003 9.20298509e-003 3.13708806e+000 -4.72656250e-001 + 2.95495749e-001 -6.87110573e-002 + <_> + -1.55182528e+000 -2.24646971e-001 -6.80893242e-001 + -2.49372125e-001 8.37015286e-002 3.59613895e-001 + <_> + 4.82812613e-001 7.95093104e-002 6.92059770e-002 3.09253901e-001 + -3.01284436e-002 -3.76868427e-001 + <_> + -3.70838083e-002 4.62383777e-002 -1.12011099e+000 4.86940473e-001 + 6.52032578e-003 -3.35109681e-001 + <_> + -8.11743736e-003 -6.54296577e-002 4.56568390e-001 -8.35073814e-002 + -3.59526843e-001 -1.35260820e-001 + <_> + -6.75484389e-002 -5.80112152e-002 1.91113681e-001 -5.30145094e-002 + -4.21618611e-001 -2.10518584e-001 + <_> + -3.90814915e-002 -2.75397971e-002 8.20111752e-001 -5.54724455e-001 + -9.20356810e-001 -4.95080389e-002 + <_> + -3.20896059e-002 -1.06405325e-001 3.33822548e-001 -2.39146933e-001 + -3.33419472e-001 -1.13001063e-001 + <_> + -5.26016176e-001 3.44599128e-001 -1.66599655e+000 1.53921023e-001 + -6.30714595e-002 4.37657654e-001 + <_> + -7.45403245e-002 3.86218101e-001 -1.37635911e+000 3.39169130e-002 + 1.97491139e-001 1.60432592e-001 + <_> + -3.90814915e-002 4.99902517e-001 -2.29059815e+000 2.58448839e-001 + 2.48179004e-001 1.82116762e-001 + <_> + 7.17897937e-002 2.40528822e-001 -1.72192439e-001 1.32847736e-002 + 5.27855493e-002 6.47063911e-001 + <_> + -4.84564304e-001 3.24653089e-001 -1.54050696e+000 2.01870993e-001 + -1.63323820e-001 9.93675590e-001 + <_> + 8.67723972e-002 -2.47081578e-001 1.90949500e+000 -1.05058819e-001 + -7.36276865e-001 1.36072874e-001 + <_> + 2.33460348e-002 -1.63422883e-001 1.70701313e+000 1.59473523e-001 + -2.10612550e-001 -4.01342809e-002 + <_> + -1.47541392e+000 -3.72385621e-001 9.27035332e-001 -1.59965336e-001 + 4.33959186e-001 -4.29566056e-001 + <_> + -2.29360580e-001 -2.97435187e-002 6.49126768e-001 -1.22047342e-001 + 8.35356534e-001 -9.40279722e-001 + <_> + -1.15992203e-001 -2.51413193e-002 9.86766160e-001 -1.12520002e-001 + 6.61178112e-001 -7.85293281e-001 + <_> + -4.04657066e-001 4.19189155e-001 -3.73915255e-001 6.39074326e-001 + -1.81564450e+000 1.92348695e+000 + <_> + -4.42113578e-001 2.04293609e-001 -9.12671268e-001 -6.03047311e-002 + -7.49697268e-001 2.92037547e-001 + <_> + -6.71846867e-001 3.05914134e-001 -1.52508795e+000 7.71835566e-001 + -1.18467271e+000 1.03163910e+000 + <_> + -6.48873508e-001 2.89419442e-001 -1.48544395e+000 7.43153453e-001 + -1.24189317e+000 1.07821929e+000 + <_> + -2.88292170e-001 1.84415445e-001 -1.06860888e+000 6.40251115e-002 + -7.04523802e-001 4.22124505e-001 + <_> + -2.98280567e-001 1.78361565e-001 -1.09726179e+000 6.68318495e-002 + -7.39297748e-001 4.59256053e-001 + <_> + -3.32740575e-001 1.89276159e-001 -1.21333396e+000 1.58590265e-002 + -5.51183581e-001 3.43864590e-001 + <_> + -2.41846085e-001 2.40074590e-001 -6.91704988e-001 -5.56952581e-002 + 1.15977682e-001 -1.18214965e-001 + <_> + -1.64435953e-001 2.51092106e-001 -5.30237675e-001 -4.65241931e-002 + 1.04529716e-001 -1.01409726e-001 + <_> + -2.23866969e-001 2.64803380e-001 -7.62729943e-001 -5.31287938e-002 + 1.15253411e-001 -1.60901934e-001 + <_> + -1.93587935e+000 9.84390974e-001 -2.85831869e-001 3.94988418e-001 + 1.12950718e+000 -1.13408852e+000 + <_> + -1.91190720e+000 3.16360220e-002 8.12913537e-001 -6.16231084e-001 + 2.45696366e-001 -4.17800963e-001 + <_> + 1.78524125e-002 1.35060817e-001 -7.67165899e-001 -1.89833358e-001 + 7.55560756e-001 -6.69018030e-001 + <_> + -7.75368437e-002 1.16376773e-001 -8.65937173e-001 -6.21677227e-002 + 3.69647890e-001 -3.49527270e-001 + <_> + -1.04505539e-001 1.10731661e-001 -7.13652670e-001 -1.29719570e-001 + 5.98651826e-001 -5.01512110e-001 + <_> + -1.22984082e-001 1.74768195e-001 -7.74279714e-001 -5.20831466e-001 + 1.69625318e+000 -2.06629801e+000 + <_> + 1.29223123e-001 2.48896420e-001 -3.56843650e-001 -1.49209112e-001 + 1.55501997e+000 -1.36678290e+000 + <_> + 6.97563350e-001 2.58816391e-001 6.66667521e-001 -1.94253579e-001 + 1.55420578e+000 -1.61594963e+000 + <_> + 1.96644843e-001 2.68054247e-001 2.11302973e-002 2.03912556e-002 + 1.34274817e+000 -1.21505082e+000 + <_> + -5.02044022e-001 -1.11371152e-001 -2.66786098e-001 5.64553201e-001 + -7.87540913e-001 2.19482350e+000 + <_> + 7.89456666e-001 -2.55082548e-001 3.67847145e-001 -6.66937530e-001 + -1.07642555e+000 1.18526518e+000 + <_> + 7.46506512e-001 -2.24430904e-001 6.58981353e-002 -1.64694405e+000 + -2.08997101e-001 1.81097493e-001 + <_> + 8.42395186e-001 -1.48546696e-001 2.16653883e-001 -7.10826099e-001 + -4.44317907e-001 9.78802145e-001 + <_> + -8.64123642e-001 7.35383272e-001 1.13178921e+000 -6.78130329e-001 + 2.94695377e+000 7.50071824e-001 + <_> + 1.87096628e-003 2.57469118e-002 3.45762372e-001 -4.81234938e-001 + -1.04797113e+000 4.98835385e-001 + <_> + -1.69929579e-001 1.08967712e-002 8.03917825e-001 -1.73675701e-001 + -1.27556002e+000 1.08758259e+000 + <_> + -1.24482341e-001 7.82675371e-002 4.47867841e-001 2.02714339e-001 + -2.20556617e-001 -8.31371695e-002 + <_> + 3.11012059e-001 8.95647109e-001 1.56021357e+000 -1.51905604e-002 + 2.33498946e-001 -1.39223444e+000 + <_> + -1.07002638e-001 3.89084190e-001 -6.97775066e-001 -3.92934322e-001 + 1.42922604e+000 -2.07485127e+000 + <_> + -9.28049386e-001 5.75085938e-001 -2.11981368e+000 -1.20615697e+000 + 1.85786402e+000 -2.39183545e+000 + <_> + -3.71195912e-001 -1.25031292e-001 2.50665307e-001 -2.80510521e+000 + 1.58294225e+000 -3.13529706e+000 + <_> + -1.07502058e-001 -1.32871762e-001 5.61559975e-001 -2.24810624e+000 + 2.23611951e+000 -3.56702709e+000 + <_> + -1.19488142e-001 -1.45774648e-001 6.63979113e-001 -7.77538419e-001 + 3.52424890e-001 -1.27550352e+000 + <_> + -3.70838083e-002 -3.74438614e-001 8.75762463e-001 -1.02288175e+000 + 1.94748342e-001 -1.35805535e+000 + <_> + -1.62497500e-003 -3.58149186e-002 1.32173562e+000 -7.08727717e-001 + 8.30895305e-001 -2.39963126e+000 + <_> + -1.28977120e-001 -4.06396501e-002 9.42243457e-001 -7.24421978e-001 + 2.20945930e+000 -3.26971769e+000 + <_> + -3.49221438e-001 -3.96475494e-001 7.29305625e-001 -9.66874242e-001 + 1.92558527e-001 -1.85112309e+000 + <_> + -2.38849565e-001 -2.73279548e-001 8.14671576e-001 -7.86331058e-001 + 1.72291374e+000 -2.13458967e+000 + <_> + 7.32522726e-001 5.33045888e-001 -5.49520612e-001 -1.55393696e+000 + -5.28768003e-001 -1.32593095e+000 + <_> + -6.33890927e-001 7.44062424e-001 -1.10716164e+000 3.13703686e-001 + 1.55138373e+000 6.69887006e-001 + <_> + -6.90325379e-001 8.44742954e-001 -4.38787079e+000 -8.36388171e-001 + 1.09336042e+000 -3.69483292e-001 + <_> + 7.67839924e-002 -2.14022294e-001 1.45881712e+000 -7.42557943e-001 + 8.65270913e-001 8.15336823e-001 + <_> + 1.30221963e-001 -2.44726986e-001 1.45681667e+000 -1.40832126e+000 + 1.32049406e+000 7.84072503e-002 + <_> + -1.14993364e-001 -1.97317705e-001 1.69495463e+000 -8.75685178e-003 + 1.47075998e-002 9.10456896e-001 + <_> + 2.48442963e-002 -3.38210344e-001 1.31911039e+000 -1.19163227e+000 + 1.67665136e+000 -2.03381225e-001 + <_> + 2.08489336e-002 -2.47870550e-001 1.06039000e+000 -9.08783972e-001 + 6.75993443e-001 -7.35246480e-001 + <_> + -2.35994644e-002 -2.36383051e-001 1.55372977e+000 -3.44000459e-002 + 1.92328668e+000 9.63053882e-001 + <_> + -3.02275926e-001 1.14302002e-001 -1.06578626e-001 1.08856440e-001 + -9.41062463e-004 -1.67347699e-001 + <_> + -2.06387252e-001 2.68607587e-001 -1.30703413e+000 -5.84618747e-001 + 3.83982182e-001 -1.97565281e+000 + <_> + -1.54447556e-001 4.43823487e-002 -1.33312523e+000 -8.02328765e-001 + 1.56546676e+000 -1.12861538e+000 + <_> + -2.22868130e-001 1.62484944e-001 -1.20220864e+000 -1.60384074e-001 + -5.45663476e-001 -9.97443497e-001 + <_> + -1.96734285e+000 -2.45482028e-001 -1.29343405e-001 + -1.29809785e+000 2.31092334e+000 -2.31134415e+000 + <_> + -1.83999074e+000 -2.65824497e-001 -4.46495295e-001 + -1.99202633e+000 1.93567586e+000 -1.39782143e+000 + <_> + -3.12323566e-003 2.77472973e-001 1.76511750e-001 -5.51382899e-001 + -1.16287613e+000 6.61325216e-001 + <_> + -1.47955090e-001 1.55066177e-001 1.13346517e-001 1.46693945e-001 + -6.17846176e-002 7.13082969e-001 + <_> + -3.28745216e-001 8.90255719e-002 1.48011610e-001 1.91616520e-001 + -2.59936541e-001 1.08707500e+000 + <_> + -5.72462261e-001 2.07507797e-002 7.20420182e-002 3.08733046e-001 + -6.77062869e-001 1.55210078e+000 + <_> + -2.58826375e-001 8.61145370e-003 3.88434827e-001 1.54857546e-001 + 6.72525242e-002 3.41089189e-001 + <_> + -2.39848405e-001 5.87511212e-002 3.70137602e-001 -5.95113188e-002 + -3.43692005e-001 7.46700227e-001 + <_> + 5.51732659e-001 1.74097434e-001 1.77935034e-001 -1.07595302e-001 + -1.33767083e-001 8.43204260e-001 + <_> + -1.77420884e-001 1.30628437e-001 7.36972541e-002 2.13399958e-002 + -4.23327088e-001 6.32897496e-001 + <_> + -2.16017831e-002 8.95763859e-002 -9.24052298e-001 5.05558476e-002 + 2.01713964e-001 -4.75246221e-001 + <_> + -4.65727933e-002 7.33203292e-002 -8.62753212e-001 2.14434519e-001 + 1.05374441e-001 -3.95368546e-001 + <_> + -1.02507852e-001 7.54538178e-002 -9.14060295e-001 -4.46354806e-001 + 1.92724481e-001 -4.23045784e-001 + <_> + 1.22231238e-001 2.49574766e-001 -6.18750691e-001 -7.57041216e-001 + 1.33803535e+000 -1.72300708e+000 + <_> + -2.01035216e-002 1.05315082e-001 -9.03704524e-001 2.08230354e-002 + 5.80957174e-001 -7.16302991e-001 + <_> + 1.03866732e+000 2.00378895e-001 7.79007554e-001 -1.07100749e+000 + 9.34716880e-001 -1.03621793e+000 + <_> + 1.91650644e-001 1.39389351e-001 -1.67421773e-001 1.98696643e-001 + 2.34439448e-001 -4.15508896e-001 + <_> + 1.13106012e+000 8.80747382e-003 1.58820236e+000 -8.73908639e-001 + 1.84425682e-001 -2.26015091e-001 + <_> + -5.64970911e-001 -6.00853600e-002 -3.35391462e-001 3.05320382e-001 + -5.86341955e-002 8.26480150e-001 + <_> + -3.83681417e-001 -8.23179558e-002 -1.22675017e-001 4.89027768e-001 + -4.03468311e-001 8.83339763e-001 + <_> + -8.27166557e-001 -3.94452251e-002 -4.65224773e-001 4.13620859e-001 + -4.10050392e-001 1.26698756e+000 + <_> + 2.82545120e-001 -4.01074857e-001 -5.29555023e-001 -1.16344929e+000 + 3.94379735e-001 -1.90015778e-001 + <_> + 4.12893802e-001 -1.81555331e-001 1.35818735e-001 -1.12787271e+000 + 4.18654054e-001 -2.62420028e-001 + <_> + 3.55959892e-001 -1.69248074e-001 -6.32327318e-001 -9.15702403e-001 + 2.53316790e-001 -2.34756604e-001 + <_> + -9.36040163e-001 -2.07446009e-001 -7.89582729e-001 + -5.61056018e-001 -1.01663265e-002 8.02276850e-001 + <_> + -8.12183917e-001 -1.65951416e-001 -1.27674437e+000 + -4.73757625e-001 -4.53625500e-001 6.84088707e-001 + <_> + 5.33112474e-002 3.32687050e-001 1.61830354e-002 3.04582745e-001 + -4.18437272e-001 3.44127476e-001 + <_> + 9.46774006e-001 6.11572742e-001 -4.93991107e-001 -8.06147933e-001 + -8.13596368e-001 1.20686471e+000 + <_> + 1.10359192e+000 5.80558121e-001 6.12956703e-001 -2.61741161e-001 + -6.63189828e-001 1.66464031e+000 + <_> + -3.26747537e-001 1.83538884e-001 -4.32544380e-001 4.02085751e-001 + -3.64905238e-001 8.04390073e-001 + <_> + 3.83286402e-002 -1.14592694e-001 -6.90711319e-001 -4.40537661e-001 + -5.07100582e-001 3.06571126e-001 + <_> + 9.71745014e-001 -6.62759170e-002 2.10704541e+000 -3.40017468e-001 + -5.97670555e-001 2.83551365e-001 + <_> + -7.75726259e-001 1.83521524e-001 -2.88017106e+000 -5.90049922e-001 + -9.15526330e-001 1.08537304e+000 + <_> + -6.80478588e-002 -5.90244532e-002 4.47145671e-001 -7.42651045e-001 + -5.13492644e-001 2.04332367e-001 + <_> + -2.16375664e-001 7.79818743e-002 -3.07916611e-001 3.54153752e-001 + 2.48048365e-001 -3.71379316e-001 + <_> + -7.61801749e-003 2.30756879e-001 -1.00909412e+000 -4.51085329e-001 + -5.50708354e-001 5.63689291e-001 + <_> + 1.21446323e+000 2.53160566e-001 3.64784420e-001 -3.58407944e-001 + 5.52836180e-001 -9.11139026e-002 + <_> + -1.18988723e-001 4.37040143e-002 1.10548985e+000 -4.97267932e-001 + 1.17479551e+000 -1.66656804e+000 + <_> + -1.25980601e-001 2.26460546e-001 -3.63063961e-001 -5.44842064e-001 + -2.98359871e-001 5.58815360e-001 + <_> + -2.41846085e-001 3.05677384e-001 -1.41728663e+000 -3.38690490e-001 + -1.11458242e+000 1.16070902e+000 + <_> + -1.70429006e-001 4.23767358e-001 -7.13963687e-001 4.44140077e-001 + -5.74019194e-001 5.40387809e-001 + <_> + -4.32624608e-001 2.73536175e-001 -1.66470242e+000 4.36093390e-001 + 4.76035118e-001 1.78263831e+000 + <_> + -7.01312661e-001 1.71482563e+000 2.90115070e+000 -8.55122149e-001 + 1.15209889e+000 4.83290166e-001 + <_> + -9.60511744e-001 1.43214166e+000 2.47023296e+000 -8.63551617e-001 + 1.94463980e+000 8.94500792e-001 + <_> + -2.25365222e-001 9.52069819e-001 3.04241800e+000 7.17819750e-001 + 7.63263404e-002 2.00192070e+000 + <_> + -4.15785909e-002 -1.67734534e-001 7.19471350e-002 -1.85686767e+000 + -9.82614636e-001 -3.41430783e-001 + <_> + -7.50397444e-002 2.99910456e-001 1.52417529e+000 3.71137381e-001 + 3.40007514e-001 -5.54930806e-001 + <_> + -1.07002638e-001 2.48245865e-001 1.41057801e+000 3.95470440e-001 + 3.48980457e-001 -5.13572156e-001 + <_> + -7.55391642e-002 -2.78277576e-001 1.56323946e+000 -9.39119995e-001 + -2.65492618e-001 7.23517478e-001 + <_> + -4.80710529e-002 -2.15533003e-001 1.19111156e+000 -7.87083387e-001 + 5.94269019e-003 4.15705562e-001 + <_> + -1.22170854e+000 6.71020329e-001 -3.85862970e+000 -1.94725251e+000 + -6.01059310e-002 -6.41435385e-002 + <_> + -2.05723858e+000 1.24314152e-001 -1.97784007e+000 -9.78303850e-001 + 8.72324884e-001 3.83516729e-001 + <_> + -1.97483420e+000 -5.75140640e-002 1.72283280e+000 -1.30515432e+000 + 5.81283927e-001 7.68445134e-001 + <_> + -2.11023632e-002 1.85052752e-001 3.88358384e-001 -8.89082968e-001 + -1.04883814e+000 6.84365988e-001 + <_> + -4.70722131e-002 1.86907515e-001 7.44281828e-001 -8.86450827e-001 + -1.43703580e+000 1.07797801e+000 + <_> + -1.88593733e+000 8.73540759e-001 -3.05705214e+000 -2.82407403e-001 + 6.11078382e-001 1.07268965e+000 + <_> + -1.64521682e+000 1.02940917e+000 1.78685462e+000 -3.89355898e-001 + 3.59923649e+000 1.55888557e+000 + <_> + -2.12466025e+000 -2.07482171e+000 1.74494123e+000 1.15486276e+000 + 1.14019001e+000 1.54943013e+000 + <_> + -1.18275368e+000 -3.16252977e-001 -1.26914561e+000 + -7.80696511e-001 -3.19809020e-001 4.24237013e-001 + <_> + -2.05973577e+000 1.14325359e-001 7.97336400e-001 -2.19603106e-001 + 3.68839830e-001 -2.54968226e-001 + <_> + 1.04565918e+000 1.44928724e-001 9.92411911e-001 -2.34688073e-001 + 2.01810375e-001 -3.53761494e-001 + <_> + 1.69890082e+000 2.17544332e-001 7.80383050e-002 -2.69397765e-001 + -2.32727468e-001 2.31378779e-001 + <_> + -4.07154173e-001 -1.05228893e-001 -1.08413851e+000 + -4.17341650e-001 5.83720028e-001 -1.01610196e+000 + <_> + 1.62248957e+000 4.86445911e-002 5.08863866e-001 -8.14042091e-002 + -2.03463018e-001 -2.86695808e-002 + <_> + 1.40224528e+000 -5.00107110e-002 2.07981952e-002 -2.92033136e-001 + 2.02972174e-001 -1.04843967e-001 + <_> + 1.77081728e+000 8.79056379e-002 6.69466674e-001 -2.87592977e-001 + -1.55742481e-001 5.07832281e-002 + <_> + 1.85771644e+000 -4.17429388e-001 -3.73812467e-001 -2.94389296e-002 + -6.41313970e-001 6.04677618e-001 + <_> + -9.75136533e-002 8.56146440e-002 -1.29295975e-001 6.86802745e-001 + 2.19178170e-001 7.51395643e-001 + <_> + -1.67931899e-001 -1.94106579e-001 3.03374618e-001 4.41578150e-001 + 4.68151607e-002 1.60880968e-001 + <_> + -1.03142941e+000 -1.86313778e-001 7.22407937e-001 7.06629395e-001 + -2.24948287e-001 1.24687147e+000 + <_> + -1.70364892e+000 5.46280816e-002 -8.87268409e-002 3.21691811e-001 + 6.11351013e-001 -2.62612760e-001 + <_> + -3.30742896e-001 -9.76262055e-003 8.91039193e-001 1.00069010e+000 + 2.42769159e-002 1.17400795e-001 + <_> + -4.97549236e-001 -2.32115120e-001 7.43688762e-001 6.58360183e-001 + -3.98038840e-003 3.60977352e-001 + <_> + -1.46242905e+000 9.26912352e-002 9.37064230e-001 7.63672531e-001 + -1.45905957e-001 1.72523499e+000 + <_> + -1.69465947e+000 3.19538295e-001 5.44891655e-001 6.26131177e-001 + -1.78116758e-003 5.11301577e-001 + <_> + -8.05192053e-001 2.54419059e-001 5.68375945e-001 5.33237576e-001 + -5.14050424e-001 6.89312696e-001 + <_> + -8.09686840e-001 -4.43027839e-002 8.46200168e-001 8.78987551e-001 + 1.42716873e+000 3.26134592e-001 + <_> + -1.42097712e+000 -7.81789143e-003 1.78385749e-001 6.55841053e-001 + -2.66994894e-001 8.33581805e-001 + <_> + -1.85647154e+000 1.09850362e-001 3.55693817e-001 7.56192207e-001 + 5.46070218e-001 -2.69151509e-001 + <_> + -2.07471824e+000 6.32901609e-001 -1.65858790e-001 4.00384068e-001 + 5.41616380e-001 -4.49957401e-001 + <_> + -2.02727318e+000 6.85441494e-001 3.26052397e-001 2.42398873e-001 + 4.99380201e-001 -5.89715838e-001 + <_> + -2.16017831e-002 -1.76520143e-002 -3.24940801e-001 + -1.57745987e-001 -4.85369951e-001 1.67771220e-001 + <_> + -1.15992203e-001 -7.56719187e-002 -3.26013535e-001 + -1.70109093e-001 -2.58372486e-001 1.66030392e-001 + <_> + -6.00571372e-002 -1.27475694e-001 7.18179867e-002 3.14416783e-003 + -4.83961701e-001 1.71423614e-001 + <_> + -1.35969013e-001 -8.45748633e-002 -1.48810893e-001 + -1.36234343e-001 -2.93441743e-001 9.76285040e-002 + <_> + -2.92786956e-001 5.01979105e-002 7.46672079e-002 -7.52927214e-002 + -4.43049401e-001 -1.51302397e-001 + <_> + -3.01277101e-001 1.12263836e-001 1.55589327e-001 2.93587637e-003 + -3.65586609e-001 -3.88103455e-001 + <_> + -1.13530874e+000 -1.01676452e+000 -1.07051444e+000 8.71748507e-001 + 2.30126381e-001 -6.22158386e-002 + <_> + 1.72087526e+000 9.10129547e-001 -9.09131467e-001 -5.09640396e-001 + -2.47263360e+000 2.12264132e+000 + <_> + 1.04715753e+000 -1.22148252e+000 3.34557891e-001 3.87391031e-001 + 9.98886287e-001 -3.89165306e+000 + <_> + 1.56705391e+000 -2.29033995e+000 -9.23237860e-001 -5.20630255e-002 + 1.86127782e-001 -3.41461015e+000 + <_> + 1.44969010e+000 -1.25204134e+000 -6.34997308e-001 -2.39438843e-002 + 1.41947031e+000 -3.57681561e+000 + <_> + -7.55250037e-001 2.76896596e-001 -1.83520809e-001 7.65372276e-001 + 6.66515306e-002 1.01262259e+000 + <_> + -6.80478588e-002 1.87836170e-001 -9.37204599e-001 2.56897181e-001 + 1.35046870e-001 7.19775930e-002 + <_> + -1.97397694e-001 2.38872275e-001 -6.72978163e-001 5.63963950e-001 + 1.05357814e+000 -5.91178179e-001 + <_> + 1.47202238e-001 1.13565266e-001 1.28850961e+000 5.75372241e-002 + 2.70540357e-001 2.74335337e-003 + <_> + 1.86420894e+000 -5.35683155e-001 2.94569468e+000 3.08657676e-001 + -6.76275611e-001 6.53700113e-001 + <_> + 1.32719055e-001 9.98287424e-002 -1.29019713e+000 -6.64265677e-002 + -1.65352607e+000 1.00291538e+000 + <_> + -1.45957410e-001 3.22837383e-002 -1.05645549e+000 -2.31775314e-001 + -6.01179957e-001 1.29160225e-001 + <_> + -1.74923778e-001 7.09270611e-002 -1.29118288e+000 3.78410891e-002 + -1.14931333e+000 1.03397715e+000 + <_> + -1.79418564e-001 2.40919515e-002 -1.68588316e+000 3.82307521e-003 + -5.64914167e-001 -1.47369966e-001 + <_> + 1.90965617e+000 -1.96887219e+000 8.19418550e-001 9.58006620e-001 + -1.54156220e+000 1.31814575e+000 + <_> + -1.86410442e-001 -6.57557786e-001 -6.94016889e-002 4.80952919e-001 + 2.32123232e+000 -2.14028072e+000 + <_> + -4.94552702e-001 -3.44141096e-001 -8.89397740e-001 1.61116421e-001 + 8.04606080e-001 -1.44058454e+000 + <_> + 1.07512498e+000 6.78761363e-001 -6.80518985e-001 -1.30615145e-001 + -1.20632434e+000 1.33283687e+000 + <_> + 6.17656112e-001 1.05913244e-001 -1.84477568e-001 -8.22202146e-001 + 3.78157318e-001 -1.13397014e+000 + <_> + 7.43510008e-001 1.95405725e-002 -2.78920978e-001 -4.85733479e-001 + -1.02006420e-001 -3.77732009e-001 + <_> + 8.14927101e-001 2.91707605e-001 -5.68287015e-001 -6.35469735e-001 + -6.17547810e-001 -3.94522160e-001 + <_> + 5.16773224e-001 2.23557925e+000 -3.23354095e-001 -7.49690652e-001 + -5.02845287e-001 -9.67589378e-001 + <_> + 1.83659911e-001 8.65824699e-001 -1.00756302e-001 -6.33662641e-001 + -9.59793866e-001 2.10388988e-001 + <_> + 6.19653761e-001 4.15787071e-001 -3.92541945e-001 -5.03115118e-001 + -4.47629988e-001 -5.53648472e-001 + <_> + 5.35751164e-001 1.61256337e+000 3.58015895e-001 -1.00136481e-001 + -1.33891606e+000 6.26649737e-001 + <_> + 1.79179299e+000 -1.53538406e+000 2.51511395e-001 -5.82686245e-001 + -2.33032703e+000 8.34600329e-001 + <_> + 1.63847101e+000 -3.08334231e-001 1.73567638e-001 -4.66515303e-001 + -2.09607863e+000 7.59054422e-001 + <_> + 1.67492867e+000 -9.56499040e-001 -1.04909658e+000 -3.35650444e-001 + -8.54784429e-001 1.29056311e+000 + <_> + 1.29037511e+000 1.11531496e+000 -3.31635535e-001 -1.31319129e+000 + 2.10497051e-001 -2.67113972e+000 + <_> + 1.87070143e+000 3.12721705e+000 2.24390554e+000 -1.66696385e-001 + 5.95769249e-002 4.59597766e-001 + <_> + 1.15253508e+000 -4.17642444e-001 -8.09506238e-001 -1.19526780e+000 + -6.42864943e-001 2.71448493e-001 + <_> + 1.54957414e+000 1.16595840e+000 -1.09942317e+000 -1.26864624e+000 + -1.16279840e+000 1.02669525e+000 + <_> + 1.52260554e+000 -2.76582527e+000 -1.17293358e+000 -7.04784036e-001 + -7.11959824e-002 -2.79784179e+000 + <_> + 1.37427771e+000 1.93203509e-001 -1.12076724e+000 -1.36843157e+000 + 3.93416882e-001 -2.17969179e+000 + <_> + 9.57261860e-001 2.35011861e-001 -1.78712940e+000 -1.02152026e+000 + -1.48515344e-001 -1.91083181e+000 + <_> + 1.64845943e+000 4.60658818e-001 5.57208419e-001 -1.16048396e+000 + 9.02449012e-001 -1.69906819e+000 + <_> + 1.80677557e+000 3.52165282e-001 -3.03248763e-001 -9.71924245e-001 + 3.21414582e-002 -8.37808311e-001 + <_> + 1.81226921e+000 -1.86421871e+000 -3.42990644e-002 -5.74525222e-002 + -2.21112743e-001 8.31707180e-001 + <_> + 1.67243159e+000 -2.44783664e+000 -1.79639399e+000 2.43613914e-001 + -2.79777002e+000 2.38701391e+000 + <_> + 9.65752006e-001 -2.61273551e+000 -2.03380990e+000 1.50266692e-001 + -2.35700202e+000 1.96284199e+000 + <_> + 7.40014076e-001 -3.15433526e+000 1.38077593e+000 -6.52724147e-001 + 5.63687682e-001 -2.43385032e-001 + <_> + 2.82545120e-001 -2.87101722e+000 6.59761965e-001 -6.00401342e-001 + 5.66238105e-001 -8.78980279e-001 + <_> + 1.46702826e-001 -3.91179800e-001 2.28459314e-001 -6.25428081e-001 + 3.98577839e-001 -3.61506581e-001 + <_> + 1.79329121e+000 -2.89235711e+000 -3.09731509e-003 -4.50450212e-001 + -9.84012961e-001 -1.83358479e+000 + <_> + 1.82425535e+000 -2.69833517e+000 1.97702241e+000 -6.68094695e-001 + -1.33429810e-001 -2.31595445e+000 + <_> + 1.80078256e+000 -2.33218813e+000 1.11124597e-001 3.05640418e-003 + -1.58624899e+000 -7.72612274e-001 + <_> + 9.18806493e-001 -3.22561097e+000 -7.25328982e-001 -8.58055055e-002 + -2.18365693e+000 1.54875171e+000 + <_> + 2.67063081e-001 -8.47153902e-001 1.01124156e+000 1.25900817e+000 + 1.46405852e+000 3.23336571e-002 + <_> + 4.08257432e-002 -8.17107677e-001 6.51670277e-001 4.15040821e-001 + 1.37262356e+000 -2.28164606e-002 + <_> + 9.32290852e-001 -3.87063789e+000 2.19526440e-002 -1.79904953e-001 + -2.69933313e-001 -1.02451611e+000 + <_> + -3.94668669e-001 -7.42821932e-001 1.62603033e+000 6.15955174e-001 + 1.47148275e+000 -2.05249086e-001 + <_> + 8.52741376e-002 -1.63026258e-001 -9.28743899e-001 -4.37154382e-001 + 1.99526340e-001 -5.11619031e-001 + <_> + -1.04505539e-001 -2.31313050e-001 -8.93398821e-001 + -6.09013200e-001 3.31980407e-001 -5.85402608e-001 + <_> + -1.34470746e-001 -1.08110473e-001 -8.90742004e-001 7.85558298e-002 + 3.32222253e-001 -4.44490880e-001 + <_> + -2.19871596e-001 1.58724576e-001 -5.75324416e-001 3.08381826e-001 + -2.23181173e-001 8.42999518e-001 + <_> + 2.88538158e-001 2.33296528e-002 1.26875713e-001 5.37392437e-001 + 1.91432476e+000 -6.41641855e-001 + <_> + -4.60733734e-002 -3.26822251e-001 -2.36427769e-001 2.75053054e-001 + 7.59610653e-001 -8.11672747e-001 + <_> + -3.65702301e-001 -2.52840489e-001 2.58672655e-001 5.98329782e-001 + 1.04496062e-001 1.04486927e-001 + <_> + -1.24981761e-001 1.10811524e-001 -1.21884301e-001 3.46728206e-001 + 1.12954414e+000 -2.10045114e-001 + <_> + 1.86620665e+000 -1.45600462e+000 1.16042113e+000 3.56851786e-001 + -1.27977788e+000 -1.07709849e+000 + <_> + 1.54807591e+000 -2.19932342e+000 4.27398801e-001 1.94192842e-001 + -8.43020439e-001 -2.23811603e+000 + <_> + 1.46217561e+000 -3.66216397e+000 -4.97043282e-001 -9.36095238e-001 + -4.99825567e-001 4.07885641e-001 + <_> + 1.02817953e+000 -5.95192671e-001 2.27802777e+000 -6.66671932e-001 + -8.35087895e-003 2.91497400e-003 + <_> + 1.52560198e+000 -3.84876251e+000 -7.74632871e-001 -9.37464297e-001 + -9.81691182e-001 4.28448588e-001 + <_> + 1.90765846e+000 -1.47263980e+000 -8.46145451e-001 6.56657100e-001 + -4.04669881e-001 -1.21962823e-001 + <_> + 1.81226921e+000 -9.49129522e-001 2.19931960e+000 5.78304268e-002 + -2.15112591e+000 1.70220590e+000 + <_> + 1.36229157e+000 -1.19373608e+000 -1.79967809e+000 -1.78052515e-001 + -2.84085393e+000 2.29203486e+000 + <_> + 1.89517295e+000 -1.50105700e-001 8.75928521e-001 -8.76659751e-001 + -1.17258728e+000 -1.05495298e+000 + <_> + 1.44705147e-001 2.81357706e-001 -9.92164791e-001 1.00070618e-001 + 2.12695017e-001 -2.23064333e-001 + <_> + 2.13625133e-001 1.96401045e-001 -9.02887404e-001 -3.20076942e-001 + 1.06337935e-001 -4.18754935e-001 + <_> + 1.41708627e-001 3.17904145e-001 -1.10205352e+000 -2.68629957e-002 + 1.26460180e-001 -4.61087555e-001 + <_> + 3.38338576e-002 2.88148314e-001 -1.22319162e+000 -5.74427024e-002 + 2.15810791e-001 -4.34659421e-001 + <_> + -6.35530800e-002 6.60508173e-004 -1.27184713e+000 -1.18494689e-001 + 2.98474133e-001 -9.39387549e-003 + <_> + 9.20804143e-001 -8.32402930e-002 1.12055314e+000 -5.73142707e-001 + 5.94538987e-001 -1.67488202e-001 + <_> + 8.71860981e-001 -8.17677751e-002 1.18109572e+000 -7.86183059e-001 + 6.83940291e-001 -2.88365960e-001 + <_> + 1.12207055e+000 -1.79875922e+000 -1.54892015e+000 -1.95101547e+000 + -7.53699690e-002 -8.09305608e-001 + <_> + -1.46956250e-001 -1.67947277e-001 -1.35964859e+000 1.62675232e-001 + 1.95348287e+000 -1.77698851e+000 + <_> + -1.39964372e-001 2.04909984e-002 -1.18938577e+000 5.66432834e-001 + 3.44251484e-001 -4.00752336e-001 + <_> + -9.00223479e-002 -1.67151596e-002 -1.00141621e+000 3.04093450e-001 + 5.68060875e-001 -9.33313072e-001 + <_> + -5.86445987e-001 -2.01148129e+000 1.24647236e+000 8.60835671e-001 + -1.26098603e-001 -2.39902899e-001 + <_> + 8.86284932e-003 1.02264941e-001 -4.72955376e-001 9.65964496e-001 + 2.59968370e-001 -1.40884280e-001 + <_> + -1.12555490e-003 -1.92816198e-001 -2.28425696e-001 5.43003619e-001 + 8.55985403e-001 -6.62950754e-001 + <_> + 8.27770382e-002 -2.91572511e-001 7.36339986e-002 -5.70464768e-002 + 1.54114807e+000 -8.66201103e-001 + <_> + -9.00223479e-002 -3.06720942e-001 4.61059660e-001 1.06706572e+000 + 1.77303445e+000 -1.08741796e+000 + <_> + -2.10882038e-001 -2.80805647e-001 1.75568148e-001 1.36676237e-001 + 2.17384171e+000 -1.28785563e+000 + <_> + -2.04888999e-001 -2.19033584e-001 1.94851145e-001 -3.91468741e-002 + 2.17395854e+000 -1.35903752e+000 + <_> + -1.33971334e-001 -5.41541696e-001 4.87076938e-001 -3.78722519e-001 + -4.09989387e-001 -6.95147395e-001 + <_> + -6.81835234e-001 -3.18164259e-001 4.47543651e-001 -3.10587436e-001 + -2.17571676e-001 -5.63060045e-001 + <_> + -1.14779425e+000 -3.08210492e-001 9.35340464e-001 -1.29908726e-001 + -1.71894860e-002 3.90066989e-002 + <_> + -1.53434551e+000 -4.39732447e-002 1.25151193e+000 8.85459781e-002 + -1.29356372e+000 1.56145895e+000 + <_> + -1.47591341e+000 -1.97267205e-001 1.05514491e+000 1.94838658e-001 + 1.07911393e-001 5.94809018e-002 + <_> + -1.31010592e+000 -2.91474015e-001 5.81019580e-001 -2.39518791e-001 + 6.50173187e-001 -3.70365143e-001 + <_> + -3.59209836e-001 -1.68215260e-001 8.37636709e-001 5.76445520e-001 + 7.48438165e-002 1.12180285e-001 + <_> + -2.02577496e+000 1.96669355e-001 -2.25638127e+000 -9.41239834e-001 + 5.19530892e-001 -3.99462253e-001 + <_> + -2.01478767e+000 2.16538981e-001 8.82826209e-001 -1.43423653e+000 + 3.98828715e-001 -4.14102703e-001 + <_> + -1.85597217e+000 -1.96404204e-001 8.52597058e-001 -3.28245699e-001 + -3.32093127e-002 -8.59826058e-002 + <_> + -1.55582058e+000 -9.61614847e-002 1.11489677e+000 3.96708697e-001 + 4.45420235e-001 -2.59541869e-001 + <_> + -1.34756243e+000 -1.55705675e-001 8.31281960e-001 4.15747195e-001 + 1.95363969e-001 -1.33178914e-002 + <_> + -8.02694917e-001 -1.32598400e-001 7.02436984e-001 1.03815997e+000 + 1.78542778e-001 -3.11814435e-002 + <_> + -1.83599532e+000 5.22303917e-002 1.10940397e+000 3.72759163e-001 + 6.92429900e-001 -4.73247498e-001 + <_> + -1.03542471e+000 -2.56451219e-001 9.71818984e-001 5.62544882e-001 + 2.02619702e-001 1.17496669e-001 + <_> + -2.12116432e+000 5.48182666e-001 -1.61450768e+000 1.06010687e+000 + 8.89436603e-001 -6.84667170e-001 + <_> + -2.03826046e+000 5.90444326e-001 -5.18513918e-001 2.02254429e-001 + 8.59402239e-001 -2.82987535e-001 + <_> + -1.38152289e+000 -1.35116160e+000 7.54611254e-001 8.48406494e-001 + 6.31224275e-001 -2.83030719e-001 + <_> + -1.75109386e+000 9.79861896e-003 1.85968742e-001 7.53510416e-001 + 1.01844251e+000 -7.87858129e-001 + <_> + 4.16389734e-001 5.15660763e-001 -3.33649248e-001 -1.33483842e-001 + -6.22785211e-001 1.98593110e-001 + <_> + -3.50861289e-002 -3.24631063e-003 -2.41910085e-001 + -1.61539778e-001 -2.40557209e-001 5.46192467e-001 + <_> + 2.74554402e-001 5.71171582e-001 -2.02342540e-001 -3.38532656e-001 + -6.04723155e-001 6.83637634e-002 + <_> + -5.19024253e-001 -1.66330189e-001 7.46329427e-002 -5.13438404e-001 + 1.90837234e-002 5.04907489e-001 + <_> + 9.57619622e-002 4.97372866e-001 -7.23555148e-001 6.00390285e-002 + -1.07523513e+000 1.02316201e+000 + <_> + 1.29722536e-001 3.97574276e-001 -8.12170923e-001 -4.39133421e-002 + -5.64830840e-001 1.84073687e-001 + <_> + -1.61081608e-002 4.40007262e-002 -5.90424478e-001 -1.71155378e-001 + -4.02766526e-001 1.48828328e-001 + <_> + -1.93402335e-001 2.16884315e-001 -7.83040881e-001 -1.83728084e-001 + -1.05605245e+000 7.78167248e-001 + <_> + -1.27478868e-001 2.83630323e-002 -5.90830386e-001 -1.69986352e-001 + -4.08284485e-001 4.41693544e-001 + <_> + -1.41462624e-001 1.76747009e-001 -8.99880052e-001 4.43929769e-002 + -3.00468355e-001 2.43108183e-001 + <_> + -2.69813627e-001 7.58524910e-002 -5.27941942e-001 -3.35298538e-001 + 1.74651980e-001 -2.54053980e-001 + <_> + -4.00661707e-001 7.93943182e-003 -1.18175268e+000 -3.71072371e-003 + -4.43019390e-001 6.86766148e-001 + <_> + -2.74807811e-001 1.37390018e-001 -6.50935531e-001 5.13487980e-002 + 6.95736408e-001 -4.35962617e-001 + <_> + -8.20316300e-002 1.49972513e-001 -4.25504357e-001 -9.72954035e-002 + -9.54114139e-001 9.40095365e-001 + <_> + 2.98384968e-002 1.37426317e-001 -1.26080379e-001 -2.33632997e-002 + -5.74419916e-001 -3.07852309e-002 + <_> + 1.77431333e+000 -6.46604061e-001 1.00817096e+000 -2.94005632e-001 + -4.29265015e-002 -1.36545032e-001 + <_> + 1.12306929e+000 -1.72042871e+000 -1.91554999e+000 8.24772865e-002 + 1.77195418e+000 -2.39537239e+000 + <_> + 1.86221123e+000 -2.01587152e+000 -6.12793922e-001 4.86552669e-003 + -1.84938014e+000 -2.98661828e+000 + <_> + 1.58403420e+000 -1.91141999e+000 -1.87358654e+000 -2.94008758e-003 + -4.43586975e-001 -2.56804705e+000 + <_> + 1.81976056e+000 -2.63339853e+000 -7.79182136e-001 9.51127291e-001 + -3.33242655e+000 2.24122930e+000 + <_> + -1.05839813e+000 -9.10615027e-002 1.25059724e+000 1.51063347e+000 + -2.20428324e+000 1.78769827e+000 + <_> + 3.40477854e-001 -4.63687807e-001 -1.26652825e+000 2.91203403e+000 + -1.31911635e+000 1.37159121e+000 + <_> + -2.17873916e-001 8.10534805e-002 -1.04837167e+000 3.24255019e-001 + 6.24883056e-001 -3.35307270e-001 + <_> + -1.14493936e-001 1.55343950e-001 -2.20739916e-001 -4.85390037e-001 + 2.96505224e-002 3.28590900e-001 + <_> + -1.07002638e-001 4.36374098e-002 -7.05054939e-001 -2.04105273e-001 + 7.50250101e-001 -4.76984620e-001 + <_> + -2.58826375e-001 1.20340727e-001 -9.58227158e-001 4.64148283e-001 + 8.28168154e-001 -5.40260792e-001 + <_> + -4.40115899e-001 2.45657433e-002 -3.80438685e-001 1.49449870e-001 + 1.15486467e+000 -5.50934613e-001 + <_> + -1.98396534e-001 1.62812591e-001 3.81850779e-001 1.04253793e+000 + 3.26101303e-001 -1.40686911e-002 + <_> + -3.75832282e-002 3.55993435e-002 -1.48816669e+000 -2.20139444e-001 + -3.51269633e-001 3.68950307e-001 + <_> + 1.22794759e+000 -2.76657414e+000 -9.41289531e-004 -7.12007403e-001 + -1.73789191e+000 1.00500429e+000 + <_> + 1.89767003e+000 -2.94810200e+000 -3.18538606e-001 1.61557531e+000 + -1.91829991e+000 1.35351455e+000 + <_> + 1.91415095e+000 -2.83296561e+000 -5.45524895e-001 1.94600725e+000 + -1.35541475e+000 1.13198411e+000 + <_> + 1.81077099e+000 -2.43330193e+000 1.08183420e+000 2.02738619e+000 + -6.42715931e-001 7.74928451e-001 + <_> + 8.23417246e-001 -2.27300048e+000 -9.29553092e-001 2.31566167e+000 + -7.35942900e-001 8.79031956e-001 + <_> + -1.21985242e-001 -1.13522321e-001 -1.17955184e+000 2.51321316e-001 + -1.28069866e+000 1.29127717e+000 + <_> + -7.30420649e-002 -3.70607935e-002 -1.32826233e+000 + -1.73562258e-001 9.77248773e-002 -3.35534483e-001 + <_> + -1.44958571e-001 4.58438136e-002 -9.32834566e-001 1.66051000e-001 + 8.11327063e-003 -1.12759545e-001 + <_> + 1.55456841e+000 -3.54197443e-001 -1.37704170e+000 8.05203915e-001 + 2.31384850e+000 -1.90610492e+000 + <_> + 1.86720538e+000 -4.49886560e-001 1.27494609e+000 2.82039195e-001 + 1.63324654e+000 -1.73769033e+000 + <_> + 1.75233877e+000 -3.81027520e-001 -5.54275513e-001 7.93061435e-001 + 2.37215066e+000 -2.15622187e+000 + <_> + 1.11358035e+000 -2.65354753e+000 9.28751171e-001 1.58056989e-002 + 1.59425366e+000 -1.20718813e+000 + <_> + 1.66743731e+000 2.81987333e+000 1.51522434e+000 -4.31376368e-001 + 3.82261693e-001 -7.38615589e-003 + <_> + 1.90266430e+000 3.32105231e+000 -3.37233812e-001 -6.07694864e-001 + -1.72135758e+000 1.84366512e+000 + <_> + 1.64646173e+000 3.24823451e+000 -2.19553843e-001 -1.31960559e+000 + -5.26582539e-001 -2.30731893e+000 + <_> + 1.35979450e+000 1.58681250e+000 2.72850245e-001 -5.11147439e-001 + -1.75340593e-001 -4.17550392e-002 + <_> + 1.28088617e+000 5.48031449e-001 6.99073851e-001 3.30644578e-001 + -5.33368528e-001 5.63904464e-001 + <_> + 1.51561368e+000 3.11141706e+000 -4.19927537e-002 -7.33273447e-001 + 3.84259731e-001 -3.72044384e-001 + <_> + 1.52510262e+000 1.52738190e+000 6.21928692e-001 -3.50784123e-001 + -1.78063476e+000 1.84038401e+000 + <_> + 1.87070143e+000 1.88866651e+000 4.79662627e-001 -2.51444370e-001 + -2.52372074e+000 2.27531314e+000 + <_> + 1.64046872e+000 8.69793355e-001 -1.35105118e-001 -1.16987014e+000 + -1.21883857e+000 2.98724584e-002 + <_> + 1.66843617e+000 2.77801573e-001 -2.40175784e-001 -1.03138375e+000 + -8.96616280e-001 -9.76779088e-002 + <_> + 9.60258365e-001 1.59888840e+000 -7.45529175e-001 -8.24647009e-001 + -3.40249360e-001 -1.60053000e-002 + <_> + 1.81676400e+000 1.70468077e-001 -3.99363220e-001 -8.03282320e-001 + -2.02304149e+000 1.17275393e+000 + <_> + -1.96898267e-001 -9.01110768e-002 -1.37343609e+000 + -3.54854643e-001 1.42150772e+000 -2.29956698e+000 + <_> + -2.33355954e-001 -2.33576596e-001 -1.07811058e+000 + -3.96170944e-001 1.18219995e+000 -2.16992188e+000 + <_> + -7.25426450e-002 -4.60890569e-002 -1.72685814e+000 + -4.59760964e-001 6.60678208e-001 -1.44430423e+000 + <_> + 1.09660006e+000 -2.72604418e+000 -2.81532973e-001 7.69281864e-001 + 1.16048932e+000 -8.28845918e-001 + <_> + 1.73985326e+000 -3.36819768e+000 -2.52379417e+000 -5.88634253e-001 + -1.17302418e+000 7.14795589e-001 + <_> + 1.63247800e+000 -9.40623641e-001 -1.67085683e+000 -6.91726685e-001 + -2.24175024e+000 -3.32451200e+000 + <_> + 1.47516060e+000 3.08125138e+000 -1.39148283e+000 -4.31602508e-001 + -7.11218297e-001 7.79829919e-001 + <_> + 5.21268010e-001 -2.59015059e+000 -1.72597516e+000 -7.22226501e-002 + -1.57927001e+000 8.28684390e-001 + <_> + 1.87319851e+000 5.83295763e-001 2.02993202e+000 -1.08222711e+000 + -6.10497534e-001 -3.92291754e-001 + <_> + 1.86071301e+000 -7.26598680e-001 1.44378555e+000 -1.00286865e+000 + -8.09981942e-001 -5.31700253e-001 + <_> + -1.34970173e-001 -9.10120666e-001 -2.56554186e-001 + -1.30382442e+000 -1.04115975e+000 -1.16499627e+000 + <_> + 1.00171018e+000 -2.75011212e-001 -7.54282415e-001 -1.38736963e+000 + 6.60784096e-002 -1.66869414e+000 + <_> + 1.69640374e+000 1.03295588e+000 1.51120222e+000 -9.28926826e-001 + -1.18186998e+000 -1.56322181e+000 + <_> + 1.37677479e+000 4.05075103e-001 -2.16111279e+000 -7.82017827e-001 + -2.02523804e+000 5.00099242e-001 + <_> + 1.83574200e+000 1.53640008e+000 2.47510344e-001 -5.25726497e-001 + -2.66497922e+000 5.70425034e-001 + <_> + 1.76232719e+000 1.61709702e+000 -5.49304485e-001 -5.43016076e-001 + -2.77779317e+000 3.82478982e-001 + <_> + -1.27478868e-001 -3.28859873e-002 3.29821497e-001 4.62405056e-001 + -9.59905148e-001 7.04386592e-001 + <_> + 1.87569559e+000 4.32878685e+000 1.45244396e+000 -1.80499244e+000 + -6.97397649e-001 7.09097147e-001 + <_> + 1.90865731e+000 -1.74547946e+000 -1.11014560e-001 -5.45240223e-001 + -2.15290475e+000 1.88634777e+000 + <_> + 1.63996923e+000 -1.71046364e+000 -2.25370336e+000 4.32321817e-001 + -8.50056708e-001 1.01045012e+000 + <_> + 1.86370945e+000 -4.03236866e+000 -1.77017903e+000 -4.79208201e-001 + -1.92456865e+000 4.99649286e-001 + <_> + 1.30835426e+000 -3.93268228e+000 -1.06232262e+000 -1.62541974e+000 + 2.72689518e-002 -2.55392075e+000 + <_> + 1.28138554e+000 -3.53896785e+000 5.54253757e-001 -1.81554675e+000 + 3.95353496e-001 -2.18139839e+000 + <_> + 1.90016723e+000 -4.01003790e+000 -1.03719244e-002 -1.56288183e+000 + -3.64357293e-001 -1.24206519e+000 + <_> + 1.17450964e+000 -3.54039216e+000 -1.05864310e+000 -1.50440693e+000 + 5.90833664e-001 -1.76776969e+000 + <_> + 2.19118759e-001 -1.67752206e-001 1.61658919e+000 -1.80444211e-001 + 5.59421480e-001 -6.65705800e-001 + <_> + -3.94169241e-001 -1.33743897e-001 -3.84497702e-001 1.48943037e-001 + -1.51124001e-001 -2.30687305e-001 + <_> + -1.00510173e-001 6.42453507e-002 8.75401378e-001 5.32116890e-001 + 5.75660288e-001 -7.87075341e-001 + <_> + 1.03611099e-002 -1.48936212e-001 1.11804652e+000 1.97887063e-001 + 6.82507992e-001 -7.34942436e-001 + <_> + -9.20200273e-002 -3.32505882e-001 -3.52481514e-001 + -2.07452148e-001 9.46384847e-001 -1.15215564e+000 + <_> + 1.82575357e+000 -3.78083682e+000 -1.65337670e+000 -7.02436149e-001 + -9.58583593e-001 -1.24177039e+000 + <_> + 9.72244442e-001 -4.12714511e-001 4.39233214e-001 -1.09356856e+000 + -2.26578370e-001 -2.50194401e-001 + <_> + 1.88218808e+000 -3.45729947e+000 -1.00754428e+000 7.54201233e-001 + -1.33821309e+000 1.06925559e+000 + <_> + 3.39479029e-001 -1.07490852e-001 -9.06332314e-001 -5.29331982e-001 + -1.67321825e+000 9.11537051e-001 + <_> + 7.97447383e-001 -4.07417345e+000 -3.16092348e+000 -4.43056285e-001 + -1.35711873e+000 -1.28760636e+000 + <_> + 6.67598128e-001 -2.46804023e+000 1.60435414e+000 9.10865724e-001 + -9.42938104e-002 -1.27366602e+000 + <_> + 1.54008520e+000 -3.87579107e+000 2.10459530e-002 -5.42837083e-001 + -4.51810956e-001 -1.87168908e+000 + <_> + 3.59955251e-001 -2.45396852e+000 1.92713559e-001 2.50228584e-001 + 1.39454588e-001 -2.14050913e+000 + <_> + -1.26122190e-002 -2.82816648e-001 -8.55301976e-001 + -5.95655143e-001 3.58229488e-001 -9.46161151e-001 + <_> + -3.76190126e-001 -8.06863531e-002 7.67175734e-001 2.90059537e-001 + 8.01952541e-001 -1.40980971e+000 + <_> + -4.50104326e-001 -1.07120909e-001 6.16931379e-001 3.16629738e-001 + 1.09044778e+000 -1.63040257e+000 + <_> + -1.66433647e-001 -2.05225721e-001 -1.22052990e-001 7.62530088e-001 + 1.64041221e+000 -1.63737178e+000 + <_> + 1.86720538e+000 -3.47179818e+000 1.82467735e+000 9.12216783e-001 + -2.16905847e-002 -2.17333749e-001 + <_> + 1.67343044e+000 -2.04630232e+000 2.56270742e+000 5.29211700e-001 + -1.05706573e+000 3.92533869e-001 + <_> + 1.19748294e+000 -3.59622765e+000 7.58319736e-001 1.01510108e+000 + 7.59944990e-002 -1.39539093e-001 + <_> + 1.87319851e+000 -2.66064835e+000 -3.67610604e-001 3.41792554e-001 + 1.79344702e+000 -1.65049243e+000 + <_> + 7.76971161e-001 -2.46643424e+000 2.15390873e+000 1.94716156e-001 + 8.46832752e-001 -1.41739583e+000 + <_> + 1.90116596e+000 -1.87573338e+000 1.48599386e-001 2.29430532e+000 + -1.78289545e+000 1.36349654e+000 + <_> + 1.10958505e+000 -4.39285100e-001 5.30872226e-001 1.05650783e+000 + -3.11264157e+000 2.65179920e+000 + <_> + 1.49913275e+000 -8.81416917e-001 6.27431720e-002 3.91690111e+000 + -2.75933027e+000 2.09937429e+000 + <_> + 1.48814547e+000 -3.22565222e+000 -2.32674494e-001 -5.48993051e-001 + 1.13859475e+000 -7.53488123e-001 + <_> + 1.37827301e+000 -3.04212761e+000 7.82117724e-001 -4.86796618e-001 + -8.84043694e-001 3.94484013e-001 + <_> + 1.81626463e+000 7.23980963e-001 9.71370876e-001 -9.80637848e-001 + 7.80198753e-001 -1.92098105e+000 + <_> + 1.84772801e+000 -1.51077271e-001 1.39059401e+000 -8.09775889e-001 + -4.64145124e-001 -6.68661773e-001 + <_> + -2.24366382e-001 -4.59713191e-002 -1.42209947e+000 + -6.05657816e-001 8.79352510e-001 -9.20491815e-001 + <_> + 1.49699345e-001 2.67383814e-001 -1.06446278e+000 3.11804726e-003 + 7.33071804e-001 -4.35824007e-001 + <_> + 3.61453503e-001 1.18028894e-001 -5.63482106e-001 1.11785922e-002 + 1.96669158e-002 -2.03664482e-001 + <_> + -1.51950449e-001 -6.41758889e-002 -1.98645365e+000 + -1.57901719e-001 -1.84819728e-001 5.39636672e-001 + <_> + -1.03342712e+000 1.03744715e-001 -6.17886126e-001 -3.12755033e-002 + 3.30754429e-001 -3.69147956e-002 + <_> + 2.90036410e-001 3.01088780e-001 -1.29306483e+000 -4.55236025e-002 + -4.13515925e-001 -2.19760220e-002 + <_> + -1.01509012e-001 1.69901162e-001 -1.29261672e+000 -1.37799010e-001 + 3.06914985e-001 6.17641270e-001 + <_> + 8.17781985e-002 1.10646442e-001 -1.01543295e+000 1.63430184e-001 + 5.96008062e-001 -9.77631435e-002 + <_> + 1.25227749e-001 4.97335866e-002 -6.85115635e-001 -7.82007277e-002 + 1.41730949e-001 1.20885976e-001 + <_> + -8.30304697e-002 1.55860052e-001 -1.19148648e+000 -3.06056198e-002 + -1.29357040e-001 4.84612226e-001 + <_> + 8.12787786e-002 -1.32868690e-002 -6.79580629e-001 -1.38092473e-001 + 2.04362094e-001 1.30497307e-001 + <_> + 2.64066577e-001 -3.00694823e-001 5.42930663e-001 -1.58693090e-001 + 3.50556612e-001 -2.72092849e-001 + <_> + 8.87842417e-001 -8.01417530e-001 9.67132628e-001 -7.55550265e-001 + 1.78433704e+000 -2.47734904e+000 + <_> + 1.65694952e+000 -2.27076694e-001 -2.92816550e-001 -8.27924252e-001 + -3.28890920e-001 5.53787768e-001 + <_> + 1.48864496e+000 7.48254895e-001 -2.27982593e+000 -5.09742320e-001 + -1.71712518e+000 1.56278372e+000 + <_> + 1.61649644e+000 3.25262636e-001 -2.79490292e-001 -7.20995605e-001 + -1.08193666e-001 -4.72677022e-001 + <_> + 1.82924950e+000 2.20025301e+000 -8.08855176e-001 -1.65187454e+000 + -3.59854549e-002 -6.67661130e-002 + <_> + 1.53708863e+000 -1.99650705e+000 1.17648028e-001 -6.60033286e-001 + -1.25217974e+000 1.55831170e+000 + <_> + 1.72287297e+000 -3.22987533e+000 -1.09723544e+000 3.64801027e-002 + 4.59256731e-002 6.27420425e-001 + <_> + -1.56087410e-002 -4.90060180e-001 -4.65024859e-002 2.56788349e+000 + 2.74002910e+000 -1.96185005e+000 + <_> + 6.60606265e-001 -3.10976863e-001 2.21145725e+000 1.92848122e+000 + 2.27423358e+000 -1.76368129e+000 + <_> + 9.12671834e-002 -5.34470439e-001 -7.47943893e-002 2.73363566e+000 + 1.13428509e+000 -8.54032934e-001 + <_> + 6.78585351e-001 -1.57043934e+000 3.47032815e-001 1.57211387e+000 + 2.18864036e+000 -1.71999598e+000 + <_> + 1.88568401e+000 -1.41613376e+000 1.72474098e+000 1.74440694e+000 + 1.49930942e+000 -1.12938547e+000 + <_> + 1.86720538e+000 -1.66188836e+000 1.16731095e+000 1.86733031e+000 + 1.44270396e+000 -1.71693897e+000 + <_> + 1.36778522e+000 -3.37342596e+000 -2.72964239e-001 1.76576424e+000 + 1.95557415e-001 -1.68682098e-001 + <_> + 3.57957572e-001 -3.90257239e-002 2.19845742e-001 2.64622886e-002 + 3.77885371e-001 -5.74901700e-002 + <_> + 4.32371169e-001 1.25773000e-002 1.14271663e-001 1.23747058e-001 + -7.73530453e-002 1.76258236e-001 + <_> + -9.79489684e-001 -8.28839242e-002 -1.04630828e-001 1.17687547e+000 + -7.06991613e-001 1.48884857e+000 + <_> + -1.03692305e+000 -5.17205931e-002 2.16706209e-002 1.09104407e+000 + -7.55508423e-001 1.20991814e+000 + <_> + -1.18624961e+000 5.17196693e-002 7.16993734e-002 2.34571052e+000 + -5.55957973e-001 2.21965384e+000 + <_> + -9.66504753e-001 -1.29862651e-001 -2.49366567e-001 9.39736664e-001 + -1.39019847e-001 7.83018887e-001 + <_> + -8.94588232e-001 -1.67920128e-001 -2.13826403e-001 8.88618708e-001 + 1.01734027e-001 4.60570335e-001 + <_> + -1.32758558e+000 3.44017595e-002 8.84220541e-001 1.80489159e+000 + -4.49561089e-001 1.53886700e+000 + <_> + -1.73611128e+000 3.61586772e-002 -7.89790988e-001 1.83341777e+000 + -8.22904408e-001 1.64951694e+000 + <_> + -1.76108229e+000 1.35573978e-002 -2.07289243e+000 2.16793609e+000 + -4.93808895e-001 1.92018878e+000 + <_> + -9.70500112e-001 -1.15143716e+000 6.74830377e-001 1.46210277e+000 + -8.05624366e-001 1.03539276e+000 + <_> + 1.80727506e+000 3.22755098e-001 8.29223022e-002 1.28323054e+000 + -2.84199548e+000 -1.66042936e+000 + <_> + 1.49363911e+000 -7.91437700e-002 -7.25392222e-001 1.40020037e+000 + -3.06813765e+000 -2.03195262e+000 + <_> + 1.18899286e+000 -5.00658512e-001 -2.00568390e+000 1.21173084e+000 + -2.51004004e+000 -1.74010432e+000 + <_> + 2.76552081e-001 -4.75689828e-001 1.38206482e+000 1.81154454e+000 + -1.43173993e+000 4.06972790e+000 + <_> + 1.87096628e-003 -5.85878730e-001 1.00722730e+000 1.35778189e+000 + -4.77293968e-001 3.65622306e+000 + <_> + -1.66933060e-001 -5.92617571e-001 4.50092405e-001 1.59940934e+000 + -1.91828954e+000 3.71755743e+000 + <_> + -5.04541099e-001 -5.84092736e-001 -3.88601542e-001 2.59491539e+000 + -1.88083556e-002 2.60337877e+000 + <_> + 4.18245830e-002 -4.77733970e-001 5.13484299e-001 1.52136755e+000 + -5.10000527e-001 3.14107156e+000 + <_> + 2.36099035e-001 -3.48383158e-001 2.65891433e+000 2.43731523e+000 + -9.73047256e-001 2.93345690e+000 + <_> + -5.87944269e-001 -1.43897146e-001 9.78806257e-001 2.45954084e+000 + -8.03395510e-001 2.61820459e+000 + <_> + -3.90814915e-002 -9.22426730e-002 -1.91317290e-001 + -7.30338573e-001 -1.33216906e+000 1.49524295e+000 + <_> + 5.72708309e-001 -1.36470056e+000 1.39219391e+000 7.28684068e-001 + -2.76266861e+000 2.16127491e+000 + <_> + 1.86071301e+000 -6.14979863e-001 5.89055896e-001 3.96001720e+000 + 3.32802534e-001 1.08645511e+000 + <_> + 1.75084054e+000 -8.90841603e-001 -1.04980814e+000 3.41918874e+000 + 1.97129726e-001 7.09684551e-001 + <_> + 6.11163616e-001 -4.84064370e-001 -1.74326825e+000 2.57195950e+000 + -2.27287501e-001 2.17544174e+000 + <_> + -1.16133792e-002 -2.02992484e-001 -5.22620380e-001 5.33788502e-001 + -8.59758556e-001 2.03023529e+000 + <_> + 1.85158178e-001 3.47716808e-001 -4.63345498e-001 9.09601212e-001 + 1.07309592e+000 1.18898988e+000 + <_> + -1.61081608e-002 5.08072197e-001 -9.59418535e-001 1.21044850e+000 + 1.00467396e+000 1.14252746e+000 + <_> + -2.81799704e-001 2.02850133e-001 -1.51771843e+000 1.78154504e+000 + -6.54178262e-001 1.79422081e+000 + <_> + -1.76921472e-001 3.31657082e-001 -1.32006514e+000 8.33429754e-001 + 9.15436223e-002 8.81956577e-001 + <_> + 6.39131188e-001 -4.04926121e-001 -5.47151089e-001 -3.33172649e-001 + -2.49723864e+000 -1.62576938e+000 + <_> + 5.47237873e-001 -4.44842838e-002 4.85655889e-002 -1.03355885e+000 + -1.71770132e+000 -1.64732611e+000 + <_> + 3.34484816e-001 -9.76688743e-001 -1.55604196e+000 2.69299436e+000 + -1.23470438e+000 1.78769588e+000 + <_> + 1.50762284e+000 -3.09679866e-001 8.27323079e-001 2.78001451e+000 + -1.08987999e+000 2.36328912e+000 + <_> + 1.15603113e+000 -1.74883521e+000 -4.28606600e-001 1.20846903e+000 + -2.01423907e+000 -6.27071083e-001 + <_> + 6.54613197e-001 -8.35542977e-001 -1.30350232e+000 1.58238924e+000 + -2.90415764e+000 3.66317487e+000 + <_> + 1.06663489e+000 -9.61284339e-001 8.20707381e-001 2.30611682e+000 + -1.91840339e+000 3.78252912e+000 + <_> + 8.14427674e-001 -8.87002349e-001 -1.94672275e+000 2.12993574e+000 + -2.10132456e+000 2.78777027e+000 + <_> + 5.96181035e-001 -1.27232349e+000 -3.94099653e-001 2.52648616e+000 + -2.47184110e+000 3.55655098e+000 + <_> + 8.61872554e-001 -2.51488161e+000 -8.82094145e-001 1.66743219e+000 + -1.68141603e+000 2.21881104e+000 + <_> + -2.16017831e-002 -3.63509178e-001 1.06046379e+000 -4.75513577e-001 + 3.94477963e-001 6.02909982e-001 + <_> + 1.61000407e+000 -7.64727220e-002 -1.17994463e+000 2.11733651e+000 + -1.38962483e+000 6.00929439e-001 + <_> + 1.78130519e+000 -2.20690918e+000 -8.60154390e-001 -2.34042263e+000 + 1.23005652e+000 -1.21994507e+000 + <_> + -9.75136533e-002 -2.78999805e-001 -2.17906505e-001 2.12737054e-001 + -9.30418610e-005 2.18527365e+000 + <_> + -1.08500898e-001 -2.18129560e-001 -5.27164400e-001 1.03398941e-001 + -5.38329065e-001 1.88089132e+000 + <_> + 8.16425323e-001 -1.83924937e+000 9.79093552e-001 2.27885175e+000 + -9.51645523e-002 1.77052307e+000 + <_> + 7.52499580e-001 -8.35669219e-001 1.73891330e+000 7.45247126e-001 + 1.06061876e+000 1.13165438e+000 + <_> + 1.02817953e+000 -5.92493176e-001 2.14095140e+000 2.67187619e+000 + 6.18653119e-001 2.45011616e+000 + <_> + 1.35280263e+000 -1.24022961e+000 -2.09569708e-001 1.62141573e+000 + 4.33064401e-001 2.08305597e+000 + <_> + 2.53578752e-001 -4.45106983e-001 -1.44204032e-002 3.21839333e+000 + 9.36243713e-001 1.49940038e+000 + <_> + 1.14554322e+000 -9.28036749e-001 2.82736087e+000 2.22254920e+000 + -2.20097804e+000 2.14541626e+000 + <_> + 1.41223359e+000 -1.30028367e+000 2.75972295e+000 1.98777866e+000 + -1.49635482e+000 1.06990564e+000 + <_> + 1.90915668e+000 2.74007869e+000 3.62652123e-001 1.36914504e+000 + -6.85772181e-001 7.31898546e-001 + <_> + 1.74135160e+000 -1.82153416e+000 -8.95502090e-001 1.46863568e+000 + -4.72803026e-001 2.42973924e+000 + <_> + 1.61399937e+000 -9.43815261e-002 4.20356125e-001 -5.34725368e-001 + -1.11697674e+000 1.46033156e+000 + <_> + 1.59402263e+000 1.37107819e-001 -1.32144892e+000 -1.26674998e+000 + -1.82384610e+000 -1.51481366e+000 + <_> + 1.80327964e+000 1.90484333e+000 -1.38286400e+000 -2.64856100e-001 + -3.35552406e+000 -1.85028100e+000 + <_> + 1.88118923e+000 2.49290466e+000 -1.10602820e+000 -7.25997984e-001 + -3.70933533e+000 -2.51813722e+000 + <_> + 1.72337246e+000 -4.01065379e-001 -6.45255893e-002 5.29764533e-001 + 1.32800758e-001 9.59841132e-001 + <_> + 1.40723944e+000 9.51557517e-001 -7.78433621e-001 1.71446335e+000 + -1.15900123e+000 2.48396492e+000 + <_> + 1.55157185e+000 -1.42360270e+000 -2.63839718e-002 2.19178945e-001 + -2.74841261e+000 2.98326087e+000 + <_> + 1.61399937e+000 -1.57514822e+000 1.30569720e+000 8.17904949e-001 + -3.40093446e+000 3.44679070e+000 + <_> + 1.60301220e+000 -1.33826697e+000 1.35798216e+000 5.63672483e-001 + -2.86288548e+000 2.07982278e+000 + <_> + 1.79379070e+000 -3.03138912e-001 1.10481513e+000 2.17863774e+000 + -9.77435648e-001 1.07192278e+000 + <_> + -6.90466985e-002 -1.83464706e-001 -1.72660780e+000 3.29611492e+000 + 7.52712414e-002 4.76004452e-001 + <_> + -7.30420649e-002 -3.38805974e-001 -1.46245253e+000 2.52202964e+000 + 1.00722408e+000 -3.46837908e-001 + <_> + 1.69091010e+000 -4.15657967e-001 4.08929847e-002 -1.59101081e+000 + -2.76010424e-001 -1.44678700e+000 + <_> + 8.05438101e-001 -1.77327788e+000 -7.30563521e-001 2.11903501e+000 + 8.04931939e-001 -3.45966578e-001 + <_> + 1.93648323e-001 -4.12319042e-003 -6.13334239e-001 -4.16853845e-001 + 7.27250934e-001 -1.49530959e+000 + <_> + -8.05333629e-002 -5.19870035e-002 -4.67570573e-001 1.53241744e-002 + 5.00526428e-001 -1.17125905e+000 + <_> + 3.52963358e-001 -6.23299591e-002 -6.95002258e-001 -1.48708761e-001 + 3.27618450e-001 -3.71995926e-001 + <_> + 3.00524235e-001 -4.44445126e-002 2.35088170e-001 -2.49866262e-001 + 8.86746347e-001 -1.56135190e+000 + <_> + -8.90235081e-002 -2.18596712e-001 3.10382992e-001 -7.76496977e-002 + 8.19567144e-002 -7.54588902e-001 + <_> + -1.20123231e+000 -1.34009048e-001 2.66735345e-001 1.06425852e-001 + -6.16160929e-001 -2.91665196e-001 + <_> + -1.00146413e+000 -4.71288115e-002 3.07697207e-001 7.90273488e-001 + 3.14725041e-001 4.82068658e-002 + <_> + -1.52785301e+000 4.28394452e-002 1.95035636e-001 -2.60786116e-002 + 5.81774175e-001 -3.93701285e-001 + <_> + -1.76308000e+000 5.74597903e-002 -1.69920146e+000 1.51712310e+000 + -2.64468879e-001 4.21252489e-001 + <_> + -1.32808495e+000 4.16431241e-002 -5.46847999e-001 1.24699378e+000 + -2.80447185e-001 3.97450387e-001 + <_> + -1.99431157e+000 1.80487171e-001 -1.31442475e+000 6.66043878e-001 + 1.61852717e-001 4.08327803e-002 + <_> + -1.79504287e+000 4.91641536e-002 -5.38484871e-001 1.38774157e+000 + -1.44527328e+000 1.36449289e+000 + <_> + 3.92417550e-001 6.50133342e-002 9.49201763e-001 2.97386497e-001 + -8.47057462e-001 9.12742436e-001 + <_> + 3.10013235e-001 1.61591649e-001 -5.57538509e-001 9.74501073e-001 + -2.76452959e-001 -6.92242086e-001 + <_> + 3.98910016e-001 2.02178732e-001 -9.96835679e-002 9.24557865e-001 + -3.58689010e-001 -1.20776139e-001 + <_> + -2.74308413e-001 4.31985974e-001 -2.62708604e-001 1.54605031e+000 + -1.79103851e-001 -2.57481113e-002 + <_> + -4.60092723e-001 1.47820085e-001 -3.74563634e-001 7.81571984e-001 + 1.54669121e-001 -2.07709387e-001 + <_> + 8.96831989e-001 3.66854131e-001 1.48648965e+000 5.23489177e-001 + 1.16561806e+000 -1.17034519e+000 + <_> + 1.28723696e-001 1.33186162e-001 -2.62163013e-001 -5.17517865e-001 + 7.22288251e-001 -1.50058544e+000 + <_> + 1.77381384e+000 4.23688507e+000 -1.11541703e-001 8.01934123e-001 + -1.52975917e+000 -1.65726769e+000 + <_> + 1.87419736e+000 2.92962837e+000 -7.65305042e-001 3.22880596e-001 + -4.91246510e+000 -2.69395399e+000 + <_> + 1.68242002e+000 2.39129829e+000 -2.11390686e+000 3.31313931e-003 + -6.04011250e+000 -3.59499145e+000 + <_> + 1.40024757e+000 2.44741988e+000 -6.86201572e-001 -5.86385243e-002 + -4.84980583e+000 -3.17299986e+000 + <_> + 1.91165388e+000 4.40101957e+000 2.57620990e-001 7.06291080e-001 + -5.93135929e+000 -3.43739843e+000 + <_> + 1.77081728e+000 2.00971794e+000 9.77770448e-001 3.15606534e-001 + -4.18655729e+000 -2.68702435e+000 + <_> + 1.82725179e+000 1.90952098e+000 -3.24026197e-001 3.17993194e-001 + -7.46253133e-001 -2.68596506e+000 + <_> + 1.86071301e+000 1.26992905e+000 -1.16323411e+000 2.46843055e-001 + -2.73489356e+000 -2.83139372e+000 + <_> + 1.85422051e+000 3.85631824e+000 -7.54132211e-001 7.56540060e-001 + -2.78725243e+000 -2.32089686e+000 + <_> + 1.76782084e+000 4.00487232e+000 2.11708498e+000 6.27686679e-001 + -5.07402897e+000 -3.21382308e+000 + <_> + 1.71338403e+000 3.98814416e+000 -2.98641992e+000 2.54229337e-001 + -6.11305952e+000 -3.27839255e+000 + <_> + -1.30461228e+000 2.45681196e-001 -1.86953545e+000 2.00402308e+000 + -1.56516814e+000 1.40895092e+000 + <_> + -6.40524998e-002 -6.33252040e-002 -2.62423962e-001 9.24334288e-001 + 1.06404376e+000 -6.53915226e-001 + <_> + 1.06249787e-001 -1.11605369e-001 4.48729359e-002 2.36093149e-001 + -1.03433385e-001 1.19885080e-001 + <_> + -5.15528321e-001 -1.03267455e+000 7.65080333e-001 -1.40181375e+000 + -2.62713850e-001 -4.52444077e-001 + <_> + 1.10658848e+000 5.64121418e-002 1.63786441e-001 -2.61576861e-001 + -5.01080640e-002 -1.73577383e-001 + <_> + 1.75169781e-001 -4.09309976e-002 -3.04831743e+000 1.97678757e+000 + -1.02348733e+000 1.03289330e+000 + <_> + 1.40709788e-001 -4.83237684e-001 1.91236269e+000 1.12001047e-001 + 1.51077652e+000 -1.73412725e-001 + <_> + 1.66743731e+000 3.87269211e+000 -2.23114714e-001 1.26504695e+000 + -1.95237029e+000 -1.14329278e+000 + <_> + 9.73243296e-001 -1.45867225e-002 2.01572239e-001 2.11756253e+000 + -1.41659820e+000 -2.98846275e-001 + <_> + 1.60101449e+000 2.48713350e+000 -1.68658686e+000 1.06641173e+000 + -2.22209334e+000 -1.58263028e+000 + <_> + 1.54308176e+000 1.33402550e+000 -1.40850437e+000 1.29137671e+000 + -1.14687300e+000 -6.52041733e-001 + <_> + 1.62348843e+000 6.13274097e-001 2.46342719e-001 -2.85767287e-001 + -4.17845808e-002 1.92143962e-001 + <_> + 1.51311648e+000 2.21331406e+000 9.84802544e-001 -3.83887082e-001 + -7.18437135e-001 -8.08863342e-001 + <_> + 1.70439446e+000 4.80807483e-001 -5.62085152e-001 1.46138346e+000 + 6.09342039e-001 -5.25589764e-001 + <_> + 1.62348843e+000 4.83702332e-001 3.68471444e-002 1.79814816e+000 + -2.68483430e-001 2.07211033e-001 + <_> + 1.72337246e+000 -2.46935673e-002 1.70749807e+000 -8.89018536e-001 + 1.14915207e-001 -2.04624963e+000 + <_> + 1.74284983e+000 -1.68552071e-001 2.70506573e+000 1.06337655e+000 + -3.11483890e-001 -1.26431727e+000 + <_> + 1.47615945e+000 -4.88383740e-001 1.55970228e+000 4.91489083e-001 + -1.56581724e+000 -7.49720156e-001 + <_> + 1.15653050e+000 1.14262927e+000 8.30119178e-002 5.29586554e-001 + -1.25941288e+000 5.45495093e-001 + <_> + 1.21795917e+000 -7.53685355e-001 -5.38640730e-002 9.54269409e-001 + -2.50286794e+000 -3.02431196e-001 + <_> + 1.26890004e+000 2.60997862e-001 -1.98022574e-001 -1.06322539e+000 + -1.61931705e+000 -2.56859040e+000 + <_> + 1.72636890e+000 3.79628867e-001 1.43315041e+000 -8.92528176e-001 + -3.40413898e-002 -1.01956546e+000 + <_> + -4.34122860e-001 -1.74013861e-002 -1.49457669e+000 + -1.71391830e-001 7.99445093e-001 -9.19608951e-001 + <_> + -2.97281742e-001 -6.86161593e-002 -1.44958484e+000 2.11839944e-001 + -3.78256381e-001 -6.13610029e-001 + <_> + 2.62068897e-001 -1.08297653e-001 1.16530252e+000 -3.85377049e-001 + 7.13470876e-001 -7.21380472e-001 + <_> + -3.80826481e-002 1.46456793e-001 -1.17195570e+000 1.91001320e+000 + -1.77019978e+000 1.14902031e+000 + <_> + 9.90723014e-001 -2.61301845e-001 7.87889957e-001 -5.46399951e-001 + -2.32096478e-001 -6.58900216e-002 + <_> + 1.06114125e+000 -1.27578306e+000 -8.80894899e-001 1.40979862e+000 + -2.19176579e+000 -1.48199332e+000 + <_> + 9.11814570e-001 -1.04520154e+000 9.18020755e-002 1.48364389e+000 + -2.00124335e+000 -9.23402071e-001 + <_> + 1.49164140e+000 -1.50442398e+000 -2.52151442e+000 8.35149407e-001 + 4.59309876e-001 -1.36300743e+000 + <_> + 1.67592752e+000 -1.10716832e+000 -1.55274200e+000 -4.81173366e-001 + -2.61989236e+000 -2.56832147e+000 + <_> + 6.58608556e-001 -3.11680466e-001 -1.03080988e+000 -6.79724455e-001 + 1.89220381e+000 -1.39238882e+000 + <_> + 1.37028229e+000 -7.53531039e-001 -2.56857800e+000 1.23238385e+000 + -1.66675448e+000 1.45781887e+000 + <_> + 1.84922636e+000 -1.70792878e-001 1.93162191e+000 8.86834085e-001 + -4.97351229e-001 -1.17840111e+000 + <_> + 1.06263947e+000 -2.53282857e+000 -1.11144209e+000 7.90676057e-001 + -1.56838000e+000 2.47669388e-002 + <_> + 1.68391824e+000 -1.26194930e+000 -1.90761900e+000 8.75000477e-001 + -2.21111774e+000 -6.08896255e-001 + <_> + 1.29986405e+000 -1.13034976e+000 -2.89234281e+000 8.69055152e-001 + -4.00420636e-001 -1.50340450e+000 + <_> + 1.88019037e+000 -6.61124647e-001 6.43032968e-001 8.48915339e-001 + -9.83462811e-001 -1.54426709e-001 + <_> + 1.52160668e+000 -1.96032298e+000 -2.44705248e+000 7.32271373e-001 + -7.95790970e-001 -6.85457468e-001 + <_> + -1.09000318e-001 8.23393464e-003 -2.14034632e-001 8.88699234e-001 + 2.00787768e-001 -2.80666500e-001 + <_> + 1.58203650e+000 4.24746513e-001 3.05303961e-001 1.74946439e+000 + 3.18126768e-001 -1.09239364e+000 + <_> + 1.04166389e+000 -5.71811080e-001 -3.93525064e-001 -9.95905995e-001 + 2.61286676e-001 -3.62732977e-001 + <_> + 2.13483535e-002 -6.74181581e-001 -2.62134016e-001 1.98088193e+000 + -3.18691730e+000 3.02047801e+000 + <_> + -1.19573867e+000 6.75884187e-002 5.66016972e-001 1.12835753e+000 + -1.09698617e+000 1.44381785e+000 + <_> + 1.24892318e+000 -8.33037615e-001 -1.96970630e+000 1.64939070e+000 + -9.29656550e-002 -1.19411005e-002 + <_> + 1.40524173e+000 -5.40299892e-001 5.07382572e-001 -6.72203362e-001 + -1.07609093e+000 -2.93029189e+000 + <_> + 1.37927186e+000 -1.08501685e+000 3.05074692e+000 1.44651401e+000 + -3.45192254e-001 3.28052156e-002 + <_> + 1.65994608e+000 -2.30097365e+000 1.95308208e+000 1.87361574e+000 + -4.41294432e-001 -5.61856069e-002 + <_> + 1.83923793e+000 -2.15335107e+000 2.11939120e+000 1.86250949e+000 + -4.99939650e-001 2.28259474e-001 + <_> + 1.07512498e+000 -2.76417527e-002 -2.20240140e+000 -6.28187358e-001 + 1.51680541e+000 -8.73500526e-001 + <_> + 1.88818109e+000 2.85023063e-001 2.87909245e+000 5.40373504e-001 + 1.19038916e+000 -2.19825530e+000 + <_> + 1.75833178e+000 -2.35541835e-001 9.46879685e-001 -1.22939932e+000 + 5.22241473e-001 -9.45840776e-001 + <_> + 1.79129362e+000 -3.60207468e-001 9.65675056e-001 -8.53345513e-001 + -3.95425349e-001 1.00024670e-001 + <_> + 1.23693717e+000 -4.69116688e-001 -9.48999465e-001 -9.36759353e-001 + -3.34266365e-001 3.87100875e-002 + <_> + -7.85356835e-002 4.62447286e-001 -2.90211344e+000 1.27029359e+000 + -2.31756425e+000 -5.27672529e-001 + <_> + -4.45751138e-002 -7.22478237e-003 -9.54334199e-001 2.96858460e-001 + 1.08567983e-001 -5.91566205e-001 + <_> + -3.87676775e-001 6.24104701e-002 2.55928874e-001 8.52693260e-001 + 1.10257995e+000 -6.28538787e-001 + <_> + -2.22368702e-001 -2.15165898e-001 2.10090920e-001 5.54858923e-001 + 9.87296104e-001 -5.55954218e-001 + <_> + 1.79029477e+000 8.49757075e-001 9.55812156e-001 -1.17387497e+000 + 1.18618262e+000 -1.91181839e+000 + <_> + 1.85671759e+000 1.27240586e+000 8.23459089e-001 1.12166978e-001 + 7.31268227e-001 -1.59575951e+000 + <_> + 1.85571873e+000 9.80079472e-001 8.57241213e-001 1.14882082e-001 + 1.06877476e-001 -6.99941576e-001 + <_> + 1.52360439e+000 -2.15417873e-002 -1.12959957e+000 -3.05970132e-001 + 1.43000800e-002 1.83963835e-001 + <_> + 1.63996923e+000 2.38805041e-001 4.61331159e-001 -5.32479346e-001 + -7.86333084e-001 9.67262387e-001 + <_> + 1.46667039e+000 9.08863395e-002 5.61807752e-001 -7.50181377e-001 + 6.58046082e-002 -2.70056844e-001 + <_> + 1.30186176e+000 7.72998512e-001 -1.42015171e+000 1.20321357e+000 + -4.79094535e-001 4.66012716e-001 + <_> + 1.82325649e+000 5.91024756e-001 -5.85134961e-002 -4.17235166e-001 + 2.06955695e+000 -1.73482049e+000 + <_> + 1.25941110e+000 6.19664490e-001 6.00782275e-001 -3.16430718e-001 + 1.90994656e+000 -1.75939822e+000 + <_> + 1.71238518e+000 -5.33729613e-001 -1.77363968e+000 1.43456626e+000 + 1.24477208e+000 -1.11179757e+000 + <_> + -1.13994516e-001 -4.10448760e-001 1.00940967e+000 -1.00695705e+000 + 9.00351822e-001 -9.16833520e-001 + <_> + 1.73585796e+000 -1.51335001e+000 9.51125547e-002 2.47118306e+000 + -8.14624906e-001 4.90378678e-001 + <_> + 1.88512523e-002 1.51663780e-001 4.87298340e-001 -1.71641540e-002 + 1.35693863e-001 -1.66471541e-001 + <_> + 1.76032948e+000 4.65516634e-002 6.84975326e-001 -2.56415233e-002 + 2.65645951e-001 -5.26780412e-002 + <_> + 1.70489383e+000 5.61181121e-002 5.16715705e-001 -6.33035898e-002 + -2.31829342e-002 1.37839526e-001 + <_> + -7.25426450e-002 5.46059832e-002 -2.83540320e+000 3.48463021e-002 + 3.46436262e-001 -2.12794527e-001 + <_> + 2.27608889e-001 -1.22840986e-001 8.04120779e-001 -1.18869819e-001 + -4.26270992e-001 4.75034088e-001 + <_> + -1.08436799e+000 -2.18208227e-002 -1.49469838e-001 + -6.99459948e-003 -1.54502347e-001 5.74289858e-001 + <_> + -9.81487393e-001 1.99657548e-002 -1.51575774e-001 1.58109535e-002 + -1.94128662e-001 5.62382936e-001 + <_> + -8.70616078e-001 6.48680376e-003 1.43135518e-001 4.26394865e-002 + -8.70516002e-002 4.65065271e-001 + <_> + -2.31857687e-001 -1.29463032e-001 7.56653965e-001 4.20818865e-001 + -3.30818668e-002 7.88739979e-001 + <_> + -2.21869290e-001 -1.41092896e-001 8.15169752e-001 1.78591549e-001 + 8.26819986e-002 6.18184626e-001 + <_> + -4.81068373e-001 -2.00027257e-001 1.13965534e-002 4.44174796e-001 + 3.30207229e-001 3.23341638e-001 + <_> + -4.76074159e-001 -1.96638733e-001 -1.14970788e-001 4.52613503e-001 + 2.44636327e-001 3.53472918e-001 + <_> + -6.59860790e-001 9.24731195e-002 -4.05596703e-001 -7.88454190e-002 + 3.53058785e-001 6.86848387e-002 + <_> + -5.22020817e-001 9.62887108e-002 -2.42645457e-001 -1.17038675e-001 + 4.94205773e-001 8.89391266e-003 + <_> + -4.05156493e-001 3.22126150e-001 7.25055346e-003 2.63738744e-002 + 1.84317708e-001 -2.78524254e-002 + <_> + -5.35505116e-001 3.41076136e-001 2.24286944e-001 1.14169814e-001 + -3.66683267e-002 3.98103297e-002 + <_> + -1.95899427e-001 2.13201448e-001 1.70091107e-001 4.61835712e-001 + -5.30090369e-002 2.66945750e-001 + <_> + -3.65202874e-001 1.19845629e-001 -4.24542308e-001 8.03164393e-002 + -2.83320606e-001 4.20921415e-001 + <_> + -3.83681417e-001 1.34874269e-001 -5.49130559e-001 8.90189037e-002 + -1.88814729e-001 3.58559668e-001 + <_> + 2.46586859e-001 1.78944573e-001 1.56731689e+000 4.17782277e-001 + -2.33742699e-001 1.64220944e-001 + <_> + 1.88512523e-002 1.82092562e-001 1.09317839e+000 4.66753513e-001 + 5.36502860e-002 -3.23201194e-002 + <_> + 5.56227446e-001 1.40713662e-001 5.83373249e-001 1.77566037e-001 + -7.29801774e-001 2.85380512e-001 + <_> + 5.73207676e-001 1.55489787e-001 4.60864633e-001 1.53632537e-001 + -8.50793362e-001 3.30677271e-001 + <_> + 2.63567150e-001 7.61780813e-002 1.05749595e+000 3.64822328e-001 + -2.87186742e-001 4.11237836e-001 + <_> + 4.17388558e-001 4.54696476e-001 1.23790622e+000 3.28807831e-001 + -3.66648167e-001 4.83089268e-001 + <_> + -5.45635149e-002 7.82989413e-002 8.66592765e-001 4.31736112e-001 + -1.57262787e-001 3.71961147e-001 + <_> + -2.47839138e-001 2.57867247e-001 4.00527537e-001 1.12582006e-001 + -3.22301567e-001 3.64806950e-002 + <_> + -2.58326948e-001 2.81841755e-001 3.99884433e-001 1.02463998e-001 + -5.69180608e-001 2.64742494e-001 + <_> + -2.66817093e-001 3.19993466e-001 3.03743601e-001 9.92335230e-002 + -4.68773663e-001 1.59292653e-001 + <_> + -1.81058422e-002 -1.06798780e+000 2.29830980e+000 -2.23418280e-001 + -3.85317028e-001 3.19136620e-001 + <_> + -1.00496018e+000 -1.02829432e+000 5.69208488e-002 2.26990551e-001 + 4.48580563e-001 -2.70904750e-001 + <_> + -8.56132925e-001 -1.04912055e+000 -1.93025231e-001 3.03181380e-001 + 3.06329668e-001 -1.63070694e-001 + <_> + -1.08436799e+000 -1.00207675e+000 -3.93743843e-001 2.65458614e-001 + 3.49217802e-001 -2.50940442e-001 + <_> + -1.30974799e-001 -8.23089242e-001 1.47438908e+000 2.75453687e-001 + 5.19384518e-002 1.65291727e-001 + <_> + 5.66715240e-001 -1.72344297e-001 1.42451310e+000 -1.42377585e-001 + -3.75893027e-001 8.73786360e-002 + <_> + 1.80128193e+000 -1.90904617e-001 2.36798748e-001 -3.38562548e-001 + -3.52350116e-001 -2.76207149e-001 + <_> + 1.34217322e-001 -3.55643451e-001 -7.13939965e-001 -2.58197844e-001 + -2.07624459e+000 2.44101405e+000 + <_> + 4.33228426e-002 -3.57568294e-001 1.27568936e+000 4.26733553e-001 + 9.49249715e-002 3.21409911e-001 + <_> + -6.85472786e-002 1.02460645e-001 -2.96488106e-001 -2.06709400e-001 + 8.23407434e-003 2.34395359e-002 + <_> + -1.76064223e-002 -8.64395127e-003 -2.86617309e-001 + -6.53698325e-001 -1.65623820e+000 1.30844951e+000 + <_> + 1.37154618e-003 4.96171117e-002 -2.27703512e-001 -6.92907393e-001 + -1.64978731e+000 1.27409256e+000 + <_> + 5.28118275e-002 1.49823204e-001 4.02290851e-001 -3.33940744e-001 + -1.98183024e+000 1.48275030e+000 + <_> + 2.26110637e-001 2.59095132e-001 3.36120903e-001 -5.20754635e-001 + -1.16797280e+000 9.45079148e-001 + <_> + 8.12787786e-002 2.56716698e-001 2.43332714e-001 -5.63067257e-001 + -1.13045907e+000 9.26398277e-001 + <_> + 1.05750367e-001 1.81727991e-001 4.11305040e-001 -5.15530884e-001 + -6.57521904e-001 3.11413914e-001 + <_> + 1.39211521e-001 2.00082183e-001 6.74084485e-001 -5.01448274e-001 + -8.23725462e-001 4.60575342e-001 + <_> + 7.72834197e-002 2.42642730e-001 5.01792192e-001 -5.20556092e-001 + -7.15244472e-001 3.64213735e-001 + <_> + 9.77596417e-002 1.27506331e-001 -3.58788818e-001 -2.87979394e-001 + -8.55830669e-001 4.28117752e-001 + <_> + 6.03031293e-002 1.29392356e-001 -3.21654052e-001 -2.63769716e-001 + -8.46352696e-001 3.39574665e-001 + <_> + -1.28477708e-001 1.86628476e-001 7.31727839e-001 -1.01563203e+000 + -3.98747057e-001 1.33629948e-001 + <_> + 3.31987709e-001 1.83199868e-001 -2.80755460e-001 -6.52397931e-001 + -7.84943402e-001 6.74015999e-001 + <_> + 2.78549761e-001 1.87985152e-001 -2.42874756e-001 -6.93281412e-001 + -8.50811720e-001 7.48856485e-001 + <_> + 1.71174407e-001 2.07384139e-001 -5.68041921e-001 -6.87359333e-001 + -8.31598282e-001 7.23706782e-001 + <_> + -3.40872891e-002 -6.48313388e-002 -1.13644719e+000 + -8.20207596e-002 -8.99780750e-001 1.11163104e+000 + <_> + 6.14160180e-001 -5.01612760e-002 -2.04005674e-001 -5.05975723e-001 + -4.42145467e-001 7.75373042e-001 + <_> + 6.19653761e-001 -2.65514925e-002 -1.08497441e-001 -4.86572534e-001 + -5.10570526e-001 7.57256567e-001 + <_> + -5.90582974e-002 1.50723130e-001 -1.53785276e+000 -1.38149917e-001 + 1.44203141e-001 1.28693551e-001 + <_> + -7.61801749e-003 1.43871918e-001 -1.60799479e+000 -1.73199743e-001 + 3.28080714e-001 -1.55057535e-002 + <_> + 5.45739591e-001 2.29701176e-001 -2.99229264e-001 -1.06575027e-001 + 3.69729280e-001 1.00966739e-002 + <_> + -3.70838083e-002 5.37103228e-002 -1.46256840e+000 -7.47746974e-002 + -7.28084124e-004 -1.68960746e-002 + <_> + 1.72672674e-001 7.86523148e-002 -9.90364611e-001 -6.21574223e-002 + 1.35267511e-001 -4.19293851e-001 + <_> + 1.29722536e-001 7.82009363e-002 -1.05988455e+000 -1.60482414e-002 + 1.55195117e-001 -4.35993195e-001 + <_> + 7.19038427e-001 1.25452384e-001 -2.68441319e-001 -2.57602721e-001 + 1.01379730e-001 3.96050811e-001 + <_> + -1.02593577e+000 1.67973787e-001 -9.30378079e-001 -2.71189094e-001 + 3.52368981e-001 -3.94819766e-001 + <_> + 6.47621334e-001 1.30200103e-001 1.00239873e+000 -3.58065479e-002 + 6.55199051e-001 -9.87003326e-001 + <_> + 4.70327109e-001 1.01472974e-001 1.08273566e+000 -2.54878215e-002 + 7.71623135e-001 -1.01501298e+000 + <_> + 5.68071865e-002 3.89480889e-002 -4.74903166e-001 -3.27107646e-002 + 5.19305050e-001 -9.26184416e-001 + <_> + 5.53230882e-001 8.95413458e-002 5.40769339e-001 -5.35443835e-002 + 7.33649671e-001 -1.02007258e+000 + <_> + -3.35878693e-002 6.62710443e-002 7.84329057e-001 1.22151412e-001 + -2.20222592e-001 -3.88757855e-001 + <_> + -1.36654031e+000 5.05410016e-001 3.62288386e-001 3.84927183e-001 + -1.82192862e-001 -7.95423150e-001 + <_> + -1.05190563e+000 6.97913826e-001 2.96743125e-001 -6.20477870e-002 + 7.56919444e-001 -1.49897206e+000 + <_> + 4.69328284e-001 2.50397652e-001 1.27784014e+000 3.56621891e-001 + 9.60098431e-002 -1.01022467e-001 + <_> + 4.90303934e-001 2.45133534e-001 1.37850392e+000 4.34481680e-001 + -1.89562544e-001 -1.03937713e-002 + <_> + -4.27630395e-001 2.96312332e-001 -3.44144702e-001 3.14030737e-001 + 1.79110855e-001 -1.88989758e-001 + <_> + -1.12482095e+000 -2.51304835e-001 7.00881958e-001 -2.93165892e-001 + 4.24837470e-002 -4.57993269e-001 + <_> + -1.16876996e+000 -2.91978449e-001 5.41660249e-001 -2.77484655e-001 + 5.53485043e-002 -3.30147684e-001 + <_> + -1.12631929e+000 -6.14579618e-001 -4.65548962e-001 + -6.04920447e-001 -5.51850140e-001 4.59626943e-001 + <_> + -1.12931573e+000 -5.56564629e-001 -6.41539544e-002 + -6.71921253e-001 -1.68973729e-001 2.20542967e-001 + <_> + -8.84599864e-001 -5.01200438e-001 2.98258662e-001 -6.27324700e-001 + -7.11425900e-001 -5.09184062e-001 + <_> + -9.61011171e-001 -4.11030352e-001 2.00285632e-002 -4.56384808e-001 + -5.90752840e-001 -3.76059800e-001 + <_> + 6.45124197e-001 -9.80380327e-002 6.47961795e-001 -2.42132843e-001 + -4.33218569e-001 -1.06190808e-001 + <_> + 4.11395520e-001 -8.22399929e-002 2.57151842e-001 -1.68861926e-001 + 1.82062656e-001 2.65271991e-001 + <_> + -4.55739535e-002 -8.26711729e-002 -3.42378765e-001 + -4.32227314e-001 -1.15032338e-001 -3.24366182e-001 + <_> + -5.00687361e-002 -6.87556788e-002 -3.31614465e-001 + -3.67104352e-001 -9.61285159e-002 -4.51565564e-001 + <_> + -7.80362636e-002 -6.53428510e-002 -4.70158875e-001 + -3.31250280e-001 -4.66626659e-002 -2.96129525e-001 + <_> + -1.09999157e-001 -5.58420569e-002 -5.27617753e-001 + -3.39878410e-001 -5.02961837e-002 -3.84680390e-001 + <_> + 2.77051508e-001 4.11163010e-002 8.44193995e-002 -3.31455946e-001 + 3.68707627e-001 -5.41054070e-001 + <_> + -3.35878693e-002 -1.02070801e-001 6.26140237e-002 -3.36109921e-002 + -4.26207840e-001 -2.33292863e-001 + <_> + -8.30304697e-002 -7.39683434e-002 -2.08976671e-001 + -5.48338354e-001 -9.56113875e-001 8.61512870e-002 + <_> + 1.22445166e+000 1.21078722e-001 7.06327319e-001 -1.98911980e-001 + -1.83863834e-001 -5.58660626e-002 + <_> + 1.19398701e+000 2.61651501e-002 7.03731179e-001 -8.24452639e-001 + 2.86086798e-002 -3.34895283e-001 + <_> + 1.86071301e+000 2.29205608e-001 1.39595771e+000 -3.63400728e-001 + -1.04309857e+000 6.39779091e-001 + <_> + 3.18361782e-002 -4.79247212e-001 -1.13507956e-001 -1.13069737e+000 + 4.59432960e-001 -2.76658058e-001 + <_> + 1.33218482e-001 -4.99679804e-001 2.00807884e-001 -1.22154582e+000 + 3.66010308e-001 -2.72171646e-001 + <_> + 1.09510183e+000 -2.45605254e+000 1.84136897e-001 1.06769848e+000 + -1.13946354e+000 1.93542790e+000 + <_> + 7.38016367e-001 -2.40503049e+000 -1.76338449e-001 -6.86309487e-003 + -1.17951417e+000 1.73731852e+000 + <_> + 2.68419757e-002 -2.26997465e-001 8.32228184e-001 -2.67165601e-001 + -4.45286324e-003 -2.44925514e-001 + <_> + -1.02507852e-001 -1.27116174e-001 -2.05774248e-001 1.71228230e-001 + -2.05643892e-001 8.42222691e-001 + <_> + -1.15492783e-001 -1.50489524e-001 -1.15176372e-001 1.23232596e-001 + -1.28148913e-001 7.18874156e-001 + <_> + -1.06503218e-001 -1.25231102e-001 -7.28017762e-002 9.67920125e-002 + -8.24026205e-003 6.63759112e-001 + <_> + -1.10498577e-001 1.19707532e-001 -5.63078821e-001 8.31949234e-001 + 8.45642805e-001 2.41984010e-001 + <_> + -1.07502058e-001 1.15191191e-001 -3.78467143e-001 5.45689225e-001 + 1.08018875e+000 -1.68127418e-001 + <_> + 1.68541646e+000 -1.04361482e-001 5.03212869e-001 -1.25711456e-001 + -2.03388244e-001 -1.31201069e-003 + <_> + 7.62845725e-002 -2.42450193e-001 5.83083332e-001 -2.08160117e-001 + -3.38344425e-001 4.24611509e-001 + <_> + -2.11023632e-002 -6.78497553e-002 3.74928974e-002 6.68101370e-001 + 4.59822565e-001 4.13631290e-001 + <_> + -1.26480028e-001 -2.94170692e-003 -2.66148239e-001 2.51741052e-001 + 5.68734705e-001 6.85567558e-002 + <_> + 7.27029145e-001 -1.63966581e-001 2.50336671e+000 2.11912781e-001 + -6.46945417e-001 5.48505902e-001 + <_> + 8.07935178e-001 -1.99679717e-001 2.75166559e+000 2.85135299e-001 + -7.66979218e-001 5.36441803e-001 + <_> + 1.10409141e+000 -2.33477160e-001 2.43383622e+000 -1.02879040e-001 + -6.10609889e-001 -1.87502086e-001 + <_> + -1.89542639e+000 -1.11782694e+000 8.96313250e-001 6.31768227e-001 + 7.15342999e-001 -4.96923238e-001 + <_> + 1.80777442e+000 2.00713173e-001 1.81430840e+000 2.35108241e-001 + 3.72055173e-002 -4.18647379e-001 + <_> + -7.05449656e-002 2.04351202e-001 -1.01678514e+000 2.08695084e-001 + -5.73271662e-002 5.26070893e-001 + <_> + 9.37642828e-002 4.60567266e-001 -9.60356832e-001 7.48934090e-001 + 2.85606533e-001 3.28772813e-001 + <_> + 8.36342853e-003 3.46463352e-001 -1.05419672e+000 7.14308023e-001 + 4.03024286e-001 3.75680953e-001 + <_> + 2.37597302e-001 2.02783689e-001 -4.19073194e-001 4.90073502e-001 + -2.83290595e-001 6.82723999e-001 + <_> + 3.36981922e-001 -6.91156685e-001 2.25845480e+000 -2.61333048e-001 + 5.53136468e-001 -3.27753350e-002 + <_> + 2.98027158e-001 -6.34336889e-001 2.69798088e+000 4.22516972e-001 + 3.31028132e-004 4.49111372e-001 + <_> + 9.12671834e-002 -3.01778138e-001 -1.20909080e-001 3.27352524e-001 + 4.70028996e-001 -3.27218622e-001 + <_> + 1.08605297e-002 6.64031431e-002 -4.95862849e-002 2.77305078e-002 + -2.54206032e-001 5.77691853e-001 + <_> + 3.28350179e-002 -1.32390708e-001 7.82014906e-001 -1.17852233e-001 + -3.32892656e-001 6.80713654e-001 + <_> + -1.31116398e-002 -9.78394896e-002 3.44404995e-001 -1.07604653e-001 + -4.37604964e-001 7.07519233e-001 + <_> + 1.55692384e-001 -1.24153145e-001 2.41977960e-001 2.59514034e-001 + -2.62086511e-001 1.12846684e+000 + <_> + 1.29223123e-001 -2.12964237e-001 1.00451255e+000 2.59537194e-002 + -1.05964735e-001 4.69949901e-001 + <_> + 1.10744573e-001 -1.28274605e-001 4.07253921e-001 1.36528492e-001 + 2.86631197e-001 2.08262559e-002 + <_> + 1.83160499e-001 -1.91260040e-001 1.58499181e-001 -6.47610053e-002 + 1.33769110e-001 1.78072840e-001 + <_> + 4.28875238e-001 2.24508792e-002 1.03258836e+000 -3.65902245e-001 + 5.88055134e-001 3.66187394e-001 + <_> + 3.96912336e-001 6.73440993e-002 4.52256352e-001 -3.14627647e-001 + 2.53341645e-001 6.28240168e-001 + <_> + 1.27788961e+000 -1.25275624e+000 5.29348731e-001 -1.02533770e+000 + -1.93761289e+000 7.16354251e-001 + <_> + 1.26540411e+000 -1.25662255e+000 -9.09563720e-001 -1.63884687e+000 + -2.01017618e+000 1.62123871e+000 + <_> + -9.75494325e-001 -3.48839052e-002 9.84203890e-002 -2.03526899e-001 + -6.65033638e-001 4.42511328e-002 + <_> + -6.47874653e-001 -7.16146901e-002 -2.34675005e-001 7.12943077e-002 + -5.56310177e-001 1.67698488e-002 + <_> + -2.09769154e+000 -6.16841353e-002 5.16212285e-001 5.99510744e-002 + -6.41407847e-001 -7.87065551e-002 + <_> + -5.11532962e-001 -4.45196368e-002 1.38957888e-001 2.79486794e-002 + -6.32015526e-001 3.49223204e-002 + <_> + -9.77492034e-001 -6.08230382e-002 1.74553394e-001 -3.75016898e-001 + -7.82099187e-001 4.53140169e-001 + <_> + -8.92091155e-001 -1.94601834e-001 4.99633551e-001 1.14206344e-001 + -1.00351393e+000 9.70980227e-001 + <_> + -8.25310498e-002 -4.30379003e-001 8.61078858e-001 -3.06509554e-001 + -7.77186811e-001 7.66964853e-001 + <_> + -5.12032390e-001 -4.10670668e-001 -1.50297448e-001 + -3.10782790e-001 -8.25715601e-001 8.36645186e-001 + <_> + -1.66819012e+000 -1.52466461e-001 -6.57287657e-001 + -2.70100892e-001 9.55379158e-002 5.06654121e-002 + <_> + -1.37103510e+000 -1.64706796e-001 8.08374435e-002 -2.71950573e-001 + -6.81504533e-002 1.47407293e-001 + <_> + -1.46442676e+000 -6.91395104e-002 -9.97810885e-002 + -2.14956775e-001 5.26014566e-002 1.01573125e-001 + <_> + -5.55981338e-001 -9.66128707e-002 1.08271980e+000 6.75860167e-001 + -1.83515632e+000 1.41119242e+000 + <_> + -3.01277101e-001 -8.24022368e-002 1.94445539e+000 6.31968260e-001 + -1.79696226e+000 1.36762428e+000 + <_> + -4.52102005e-001 -1.76104978e-001 2.36766887e+000 1.24688196e+000 + -9.27362978e-001 8.16009939e-001 + <_> + -1.99730802e+000 -3.94165032e-002 -1.09934139e+000 1.68220252e-001 + -7.95734167e-001 8.75587046e-001 + <_> + -1.84548438e+000 -1.68445781e-002 -3.14869672e-001 5.72156347e-002 + -6.06931746e-001 7.98500478e-001 + <_> + -1.48090756e+000 -1.37202591e-002 -1.51790559e+000 + -1.17999474e-002 -4.11905497e-001 6.13672376e-001 + <_> + -1.48839891e+000 -7.20268674e-003 -1.50103688e+000 + -1.28962891e-002 -4.62053925e-001 5.80248713e-001 + <_> + -1.61125624e+000 2.18953323e-002 -1.32708943e+000 5.78752812e-003 + -3.91721010e-001 6.10240221e-001 + <_> + -2.75806665e-001 3.40790153e-001 9.93460953e-001 2.97579449e-002 + 2.62982637e-001 -1.43305302e-001 + <_> + -2.94285208e-001 3.38890254e-001 1.79703379e+000 1.26961805e-002 + 3.90016258e-001 -1.37096688e-001 + <_> + -3.50719690e-001 2.81901568e-001 -1.76570769e-002 2.46550053e-001 + -2.43951514e-001 5.33384442e-001 + <_> + -2.82299131e-001 2.28207678e-001 2.44835079e-001 2.27494270e-001 + -3.31207454e-001 6.17330253e-001 + <_> + 2.20617011e-001 8.53354484e-002 1.31803238e+000 5.56220531e-001 + -1.08069725e-001 1.80150554e-001 + <_> + -2.43843764e-001 1.36845350e-001 -1.63895667e+000 1.82524025e-002 + -4.16410685e-001 1.05635926e-001 + <_> + -3.90173882e-001 1.27344877e-001 -3.57544750e-001 8.27489719e-002 + -2.31996089e-001 3.06601405e-001 + <_> + -4.02159989e-001 1.17623441e-001 -2.60444522e-001 5.01839183e-002 + -2.49116197e-001 3.04961979e-001 + <_> + -1.78919151e-001 3.06908607e-001 5.47934417e-003 -8.51545706e-002 + -4.33855444e-001 1.88847959e-001 + <_> + -2.44842619e-001 3.14844400e-001 -2.88296252e-001 -8.84285942e-002 + -6.24241352e-001 3.69218826e-001 + <_> + -4.35762703e-002 -1.38641959e-002 -4.33667213e-001 1.32885277e-001 + -1.40900254e-001 1.24865793e-001 + <_> + -4.15785909e-002 1.42073240e-002 -3.87892514e-001 1.71681806e-001 + -2.13356912e-001 2.54067719e-001 + <_> + -6.02427423e-001 -8.55999529e-001 1.65301993e-001 3.46068799e-001 + 1.22766042e+000 -9.34780717e-001 + <_> + -9.32044744e-001 -9.03620481e-001 -7.61712551e-001 + -1.53354362e-001 1.02638566e+000 -8.67054403e-001 + <_> + -3.87676775e-001 -9.20426726e-001 3.03437859e-001 -4.00686383e-001 + 1.85476291e+000 -2.42105126e+000 + <_> + -1.50452197e-001 -1.17089021e+000 1.54239607e+000 -9.77910399e-001 + 5.98073840e-001 -1.18910921e+000 + <_> + 2.73555547e-001 -6.35439530e-002 3.92538637e-001 -4.37255144e-001 + -4.56525773e-001 1.29900932e-001 + <_> + 1.94147736e-001 -6.04625642e-002 1.81493267e-001 -5.08743465e-001 + -3.11414599e-001 1.12908281e-001 + <_> + 2.94031769e-001 -6.88273311e-002 2.29516238e-001 -5.14581203e-001 + -3.17467511e-001 1.52090028e-001 + <_> + 2.76552081e-001 -9.87302586e-002 1.87291875e-001 -4.92025763e-001 + -3.07606936e-001 1.63214564e-001 + <_> + 1.89816952e+000 1.08903635e+000 2.02865887e+000 5.86732090e-001 + -4.33887951e-002 2.15769976e-001 + <_> + 2.71058440e-001 -3.72606248e-001 -1.57805026e+000 6.68495476e-001 + -2.89226627e+000 2.63939619e+000 + <_> + 8.09932888e-001 -7.08864093e-001 3.27468610e+000 -1.17203116e-001 + 1.60322383e-001 -3.29998046e-001 + <_> + -9.45171341e-002 -1.85430981e-002 -1.36150670e+000 + -2.84367740e-001 -9.26726758e-001 6.08017385e-001 + <_> + -1.38466105e-001 -1.05671749e-001 -1.68548775e+000 + -1.69774085e-001 -1.01089108e+000 6.28426671e-001 + <_> + -1.81058422e-002 2.26440579e-002 -3.40162128e-001 -4.52446669e-001 + -5.55577159e-001 3.74627233e-001 + <_> + -1.96041018e-002 -1.02331527e-001 -4.54708219e-001 + -6.24766350e-001 4.10481468e-002 1.51542500e-002 + <_> + -4.85704727e-002 -1.21957161e-001 -5.52715123e-001 + -5.67283928e-001 -8.93687457e-002 7.98188597e-002 + <_> + -7.10443854e-002 1.29954532e-001 4.50643271e-001 -4.17039484e-001 + -6.84822917e-001 4.66763735e-001 + <_> + -2.40988843e-002 1.25955850e-001 5.47435105e-001 -5.27359724e-001 + -1.10121346e+000 7.47522891e-001 + <_> + 8.12787786e-002 -1.70372159e-003 -2.69337475e-001 -6.74176753e-001 + 2.44125977e-001 -6.05897307e-002 + <_> + 3.13367583e-002 -9.97273996e-002 -1.17698766e-001 -6.78260148e-001 + 2.26551350e-002 7.11258203e-002 + <_> + -2.44842619e-001 -1.08971886e-001 3.46695423e-001 -4.46995020e-001 + -1.28790545e+000 9.37595308e-001 + <_> + -2.69813627e-001 -1.84989069e-002 4.18716699e-001 -4.81852680e-001 + -1.25767267e+000 9.83578384e-001 + <_> + 3.93274799e-002 9.37388837e-002 -9.50789452e-002 -4.22423005e-001 + -4.78754252e-001 1.80607826e-001 + <_> + 6.47979081e-002 6.22390695e-002 3.13569233e-002 -5.46428919e-001 + -3.25560719e-001 8.74186680e-002 + <_> + -1.06003799e-001 -3.04643631e-001 6.62890553e-001 -6.87242508e-001 + -2.57111013e-001 1.90251961e-001 + <_> + -9.25194547e-002 -3.27282786e-001 8.26822281e-001 -3.78855526e-001 + -9.54121709e-001 6.66243374e-001 + <_> + -2.90930867e-002 -3.72037441e-001 9.03896272e-001 -5.20702004e-001 + -6.58559740e-001 3.55018705e-001 + <_> + -5.40640950e-002 -3.50552619e-001 8.18045318e-001 -1.02722478e+000 + -5.62527478e-001 8.70546401e-002 + <_> + 1.68841302e+000 -1.85132146e+000 1.55771756e+000 -1.11383522e+000 + -7.37004042e-001 1.03692842e+000 + <_> + 9.36226919e-003 -6.45974427e-002 -5.33838034e-001 -4.29397136e-001 + -2.63366282e-001 6.76239192e-001 + <_> + 9.06820416e-001 9.11664814e-002 9.60904062e-002 2.48674843e-002 + -3.58317010e-002 3.63476634e-001 + <_> + 6.13161325e-001 8.99569020e-002 -1.37500986e-001 1.02869660e-001 + 1.75093681e-001 2.10373029e-001 + <_> + 6.02673471e-001 8.76491740e-002 -7.86926299e-002 1.10486157e-001 + 2.22904339e-001 2.27123603e-001 + <_> + 6.06668830e-001 2.29178935e-001 -3.01005751e-001 1.69908926e-002 + 1.12290037e+000 -6.83844030e-001 + <_> + 7.46506512e-001 1.71535283e-001 2.11669713e-001 -3.55355181e-002 + 9.34884608e-001 -5.11009514e-001 + <_> + 6.95066214e-001 1.64941028e-001 8.55211318e-002 -1.66315977e-002 + 1.12607968e+000 -5.86167157e-001 + <_> + 7.31523931e-001 1.32769197e-001 3.14555377e-001 3.69449258e-002 + 9.94013548e-001 -5.69597542e-001 + <_> + -9.00223479e-002 1.56287596e-001 -9.28725481e-001 5.51770814e-002 + 7.65852258e-002 -1.51163135e-002 + <_> + 7.02915341e-002 1.65199220e-001 -5.21205008e-001 1.32262975e-001 + -4.32373509e-002 9.15188044e-002 + <_> + 5.15774369e-001 7.94281885e-002 5.80824494e-001 -4.22733501e-002 + -5.77263869e-002 4.52634245e-001 + <_> + -6.16910636e-001 3.64091471e-002 -3.81841287e-002 -3.78373981e-001 + 5.58113635e-001 1.39134109e-001 + <_> + 1.30685592e+000 7.17625797e-001 1.34945931e-002 -4.38065618e-001 + 7.48768330e-001 -9.16135371e-001 + <_> + 1.31984091e+000 7.93099225e-001 -1.56154037e-001 -4.59177315e-001 + 8.08921039e-001 -8.62454295e-001 + <_> + 2.92034090e-001 -1.44417346e-001 5.53468287e-001 2.10296333e-001 + 7.25838691e-002 -1.86746851e-001 + <_> + 3.14008594e-001 -1.69240981e-001 7.01198220e-001 2.34559923e-001 + 1.54447779e-001 -1.79895848e-001 + <_> + -6.76841080e-001 -7.37808496e-002 -3.85351688e-001 5.76907575e-001 + 8.97065476e-002 1.06594972e-001 + <_> + -7.56748259e-001 1.31817721e-002 1.37837470e+000 4.52063382e-001 + 4.57132280e-001 -6.81207538e-001 + <_> + -6.47375286e-001 2.56984830e-001 6.37827396e-001 2.34432817e-001 + 8.91947329e-001 -4.98432457e-001 + <_> + 1.15453279e+000 1.95946828e-001 1.29312742e+000 -6.04031235e-002 + -3.76393825e-001 -9.66676176e-002 + <_> + 1.13006127e+000 2.40874454e-001 1.02607810e+000 -9.37379673e-002 + -4.18050289e-001 5.85286599e-003 + <_> + 1.10009599e+000 2.03605950e-001 1.13574278e+000 -1.01055518e-001 + -3.44193101e-001 1.37009015e-002 + <_> + -1.16876996e+000 2.93225213e-003 -2.40148278e-003 -2.36813217e-001 + -2.78579980e-001 -1.52770638e-001 + <_> + -1.12132502e+000 -1.21008093e-002 -3.58620510e-002 + -2.69727409e-001 -3.72282714e-001 -1.33891419e-001 + <_> + -5.30652553e-002 -3.42562377e-001 1.19317007e+000 -1.49869323e-001 + -4.04136330e-001 9.04624984e-002 + <_> + -1.83913350e-001 -3.20934892e-001 1.00918305e+000 -1.27684638e-001 + -5.65675080e-001 1.12573527e-001 + <_> + 2.33460348e-002 -3.80936593e-001 2.02460527e+000 -1.83694914e-001 + -5.83295941e-001 1.98157802e-001 + <_> + -8.47642779e-001 -2.94278592e-001 -1.47487760e-001 + -3.50518733e-001 -3.83162767e-001 -2.17766970e-001 + <_> + 9.40780997e-001 6.97037458e-001 1.73337042e+000 -7.70028913e-003 + -1.55250177e-001 -4.82139617e-001 + <_> + 1.18549693e+000 6.37804806e-001 2.18614626e+000 -8.24572425e-003 + -2.05879003e-001 -4.51988250e-001 + <_> + -4.30127501e-001 -1.60585493e-001 -2.44905829e+000 7.00603276e-002 + 7.54915714e-001 1.67159483e-001 + <_> + -1.18988723e-001 -1.80900663e-001 -1.25824940e+000 + -1.43580481e-001 8.99664283e-001 -7.94361711e-001 + <_> + 3.64949465e-001 1.03157923e-001 5.86233020e-001 -7.47531578e-002 + -1.00317550e+000 4.35320884e-001 + <_> + 1.20732971e-001 1.04165167e-001 8.30119178e-002 -3.65098953e-001 + 3.70040119e-001 -2.67424673e-001 + <_> + 5.48095070e-002 1.24296159e-001 4.35063481e-001 -3.46297026e-001 + 1.99811935e-001 -2.19931260e-001 + <_> + 8.85345340e-001 -6.82746246e-002 -3.03441381e+000 6.21425211e-001 + -2.09419489e+000 1.64748633e+000 + <_> + 9.44776356e-001 -2.15061426e-001 -1.90676248e+000 4.64482009e-002 + -2.63766766e+000 2.12224603e+000 + <_> + 1.09510183e+000 -2.16830328e-001 -1.76624656e+000 1.09588362e-001 + -2.71182489e+000 2.09914255e+000 + <_> + 1.90266430e+000 1.86584443e-001 -7.94606447e-001 -2.65600979e-001 + 4.66879010e-001 -2.62192404e-003 + <_> + 1.85971415e+000 8.41609776e-001 -2.20929646e+000 9.42481086e-002 + -1.88127026e-001 2.80171514e-001 + <_> + -1.20486982e-001 3.67420018e-002 -8.11047014e-003 -3.57341617e-001 + -1.53389740e+000 1.44254625e+000 + <_> + -5.35646752e-002 -1.27067016e-002 7.66914412e-002 -1.71157211e-001 + -1.54124880e+000 1.53716648e+000 + <_> + -8.65264088e-002 -3.69964018e-002 3.62330191e-002 -2.88246512e-001 + -1.59846914e+000 1.56598639e+000 + <_> + 1.85921466e+000 -9.92152542e-002 9.72116768e-001 -3.96392852e-001 + -5.47339201e-001 5.65816402e-001 + <_> + 1.83374429e+000 -1.46346599e-001 9.12422836e-001 -3.27653021e-001 + -6.64154887e-001 5.33507764e-001 + <_> + 8.27770382e-002 -1.23339641e+000 -2.46157527e+000 -5.02663888e-002 + -2.26624751e+000 2.06732774e+000 + <_> + 1.68535728e-002 -1.27311492e+000 -3.01495957e+000 1.42843115e+000 + -2.35613155e+000 2.19752383e+000 + <_> + -6.61917683e-003 -1.31794429e+000 -2.74473166e+000 1.27798080e+000 + -2.96455407e+000 2.43692446e+000 + <_> + 1.88768172e+000 1.59356618e+000 -1.71908545e+000 -7.49825537e-001 + -3.06254292e+000 2.10223889e+000 + <_> + 1.41822672e+000 -1.00790143e+000 -2.55402613e+000 -9.50583577e-001 + -2.05798531e+000 2.92107582e-001 + <_> + -1.26979440e-001 -3.31160985e-002 -3.12824339e-001 + -4.32596244e-002 1.16130841e+000 -7.17170060e-001 + <_> + -9.20200273e-002 -3.69847231e-002 -1.76717982e-001 + -3.55061777e-002 1.07133830e+000 -6.61107540e-001 + <_> + -5.15669957e-002 -2.66752280e-002 3.82435918e-001 -5.06403565e-001 + 7.66114950e-001 -8.07835519e-001 + <_> + -4.40756902e-002 -7.55554438e-002 2.94884920e-001 -7.68897295e-001 + 6.54767036e-001 -7.56429076e-001 + <_> + -1.00010753e-001 -4.29805182e-002 3.18153143e-001 -7.79991865e-001 + 7.26763070e-001 -6.85090244e-001 + <_> + 1.03611099e-002 -3.17809790e-001 4.23468918e-001 -3.99489403e-001 + 4.04322624e-001 -4.33282763e-001 + <_> + 4.83170450e-002 -3.05961788e-001 5.60756087e-001 -5.53686380e-001 + 6.23303592e-001 -5.42600095e-001 + <_> + 3.13367583e-002 -2.74132460e-001 5.77653766e-001 -5.16709089e-001 + 5.14014900e-001 -4.88121331e-001 + <_> + -2.12439522e-003 -4.07784969e-001 4.13260758e-001 -7.60568559e-001 + 1.21746206e+000 -9.68252778e-001 + <_> + 2.18477733e-002 -2.49250591e-001 9.61774230e-001 -7.07025051e-001 + 1.04877079e+000 -8.04399788e-001 + <_> + 2.19118759e-001 -4.13001448e-001 1.59618866e+000 -1.33666426e-001 + -5.07671714e-001 6.23597205e-001 + <_> + 2.39095554e-001 -4.25712079e-001 1.44094431e+000 -8.47507045e-002 + -5.27239859e-001 5.55029094e-001 + <_> + 9.50269938e-001 -1.89212084e-001 -2.79105449e+000 -3.70817512e-001 + -9.85543072e-001 1.75623417e-001 + <_> + 5.98037094e-002 -5.25015183e-002 -3.21412301e+000 4.44037884e-001 + -5.05186379e-001 7.19495192e-002 + <_> + -2.57328123e-001 -1.40840694e-001 -3.47843122e+000 4.58263427e-001 + 3.61157358e-001 -3.01283419e-001 + <_> + 3.40977281e-001 5.18968463e-001 2.13183713e+000 -1.82672113e-001 + 1.77626312e-001 -3.23296785e-001 + <_> + 5.68712950e-001 5.21790385e-001 2.49968457e+000 -3.04546446e-001 + 1.60004765e-001 -3.52266431e-001 + <_> + -6.25542402e-002 2.21954942e-001 9.50227082e-001 -1.87803969e-001 + -2.27662981e-001 -5.64960763e-002 + <_> + -2.06387252e-001 1.04272805e-001 -9.13272202e-001 2.27880124e-002 + 1.38824120e-001 1.19968966e-001 + <_> + 5.06285369e-001 4.92306612e-003 -3.35749924e-001 3.23474348e-001 + 3.11972350e-001 -2.13873073e-001 + <_> + 1.88512523e-002 -1.95947289e+000 -1.17505002e+000 -1.03374708e+000 + -3.86838317e-001 8.30250829e-002 + <_> + 1.22744811e+000 -1.85097742e+000 -2.43500447e+000 3.71258259e-001 + 3.20132256e-001 -3.05389702e-001 + <_> + 1.06663489e+000 -4.42740582e-002 -2.93143868e+000 1.10903203e-001 + 7.50927255e-002 4.73105311e-002 + <_> + -2.29360580e-001 2.57215589e-001 -2.15801597e+000 1.42305866e-001 + 5.55573046e-001 1.79356158e-001 + <_> + -2.47339711e-001 1.39055550e-001 -2.35672593e+000 -1.88901544e-001 + 8.09471309e-001 -1.56603783e-001 + <_> + -9.80130732e-002 5.92785738e-002 -2.27687669e+000 1.58661678e-001 + -4.93200779e-001 6.80681229e-001 + <_> + 1.83160499e-001 -4.16868478e-001 -1.50266206e-002 -6.91380739e-001 + 1.79803300e+000 -1.87935019e+000 + <_> + 1.89153537e-001 -2.82133579e-001 6.69843614e-001 -1.61910832e-001 + 8.55854094e-001 -2.90864319e-001 + <_> + 4.06900734e-001 -3.50034624e-001 1.27569211e+000 -4.55556512e-001 + 1.65297925e+000 -1.08761418e+000 + <_> + 3.79432648e-001 -3.33200306e-001 1.33460057e+000 -7.03353047e-001 + 1.46886563e+000 -1.08947253e+000 + <_> + 9.82590616e-002 -1.96071506e-001 8.52346659e-001 -3.77198942e-002 + 5.77000380e-001 -3.22323650e-001 + <_> + 9.86168906e-003 -2.10163459e-001 2.54025877e-001 8.34635831e-003 + -7.09587038e-002 4.34916979e-003 + <_> + 3.55959892e-001 1.97312653e-001 2.17884764e-001 7.66051829e-001 + -1.21705341e+000 1.15107751e+000 + <_> + 9.86228228e-001 -1.22943342e+000 -7.75262833e-001 -1.20330438e-001 + -3.13499379e+000 2.39219022e+000 + <_> + 1.05250947e-001 -8.06172267e-002 4.71539319e-001 -3.79735380e-002 + 2.38700524e-001 -5.57819188e-001 + <_> + -1.17490463e-001 -1.37698695e-001 2.78469622e-001 6.49629712e-001 + -1.11082077e+000 1.70110023e+000 + <_> + -2.18872756e-001 -8.26001540e-002 1.46717474e-001 4.89163846e-001 + -1.01502633e+000 1.06182897e+000 + <_> + 8.03440392e-001 9.66056287e-002 1.39776051e+000 -2.55925059e-001 + 3.18964332e-001 -6.15166903e-001 + <_> + -2.58826375e-001 4.51850481e-002 -3.61746103e-001 -2.24800333e-001 + 2.58878708e-001 -4.72447604e-001 + <_> + -2.32357100e-001 1.55280828e-001 -3.03667843e-001 -5.40210187e-001 + 8.96562755e-001 -1.76761496e+000 + <_> + -9.99466479e-001 1.50939032e-001 -1.79382712e-001 1.47444463e+000 + 5.18191814e-001 -6.28709733e-001 + <_> + 1.21346438e+000 3.79059005e+000 -5.57543814e-001 5.10338545e-001 + 5.99092692e-002 -2.31976724e+000 + <_> + -3.23751003e-001 -5.51493227e-001 8.15723240e-001 -3.12814593e-001 + -4.14392576e-002 -8.43701214e-002 + <_> + -1.33471906e-001 -3.33291627e-002 -3.74647975e-001 4.88783717e-001 + 1.06724119e+000 -6.64179027e-001 + <_> + -9.20200273e-002 -6.78961575e-002 -5.22438526e-001 6.69261277e-001 + 5.83768964e-001 -2.34249666e-001 + <_> + -2.88791597e-001 8.45489092e-003 -4.40736234e-001 6.94517255e-001 + 1.27924526e+000 -8.56403053e-001 + <_> + 3.72440755e-001 -1.60174981e-001 -4.12336260e-001 1.68217212e-001 + 1.03677154e+000 -8.62053931e-001 + <_> + 1.27724856e-001 -4.54242937e-002 -9.92865920e-001 1.45502418e-001 + 1.11263645e+000 -1.21717823e+000 + <_> + 2.73413956e-002 -6.21853881e-002 -1.50252640e-001 2.12096468e-001 + -1.85642958e+000 1.52054894e+000 + <_> + 1.61685437e-001 -2.70325448e-002 -1.78811140e-002 2.95328021e-001 + -2.17345262e+000 1.72581482e+000 + <_> + -1.86410442e-001 -2.76603371e-001 1.88169569e-001 1.28159487e+000 + -2.34454036e+000 2.81520081e+000 + <_> + 1.53694704e-001 8.52147117e-002 -6.18052185e-001 7.34700680e-001 + -1.06221914e+000 3.16643524e+000 + <_> + -4.35621113e-001 -1.46351079e-003 -1.65148950e+000 2.34429693e+000 + -2.12243104e+000 3.58322382e+000 + <_> + 1.95146590e-001 6.96423575e-002 -6.68695092e-001 3.25245559e-001 + -1.47530234e+000 1.99701619e+000 + <_> + -1.38965532e-001 2.50105053e-001 -1.17473900e+000 2.14422494e-001 + -8.80012512e-001 5.29807270e-001 + <_> + -8.02195549e-001 -6.33444548e-001 3.09971839e-001 7.66277492e-001 + -1.48105472e-002 4.23657298e-001 + <_> + 7.59990871e-001 -6.39376581e-001 8.16092253e-001 -2.89544284e-001 + -1.75045037e+000 -7.21605182e-001 + <_> + -1.36468425e-001 -4.94325876e-001 -1.77730490e-002 + -9.38544989e-001 -1.34265268e+000 5.43545842e-001 + <_> + 2.50582218e-001 -3.24880362e-001 8.47198740e-002 -1.22386718e+000 + -1.22120428e+000 2.12266803e-001 + <_> + 1.12242833e-001 3.14387172e-001 -1.45772398e+000 1.61715135e-001 + -7.38939047e-001 -1.35645199e+000 + <_> + -8.60269889e-002 2.36282989e-001 -1.21487594e+000 9.81344700e-001 + -2.05074832e-001 -5.73138297e-001 + <_> + 1.72337246e+000 3.89013499e-001 -7.45044574e-002 1.60719085e+000 + -2.50841951e+000 2.22336316e+000 + <_> + 4.79316682e-001 -5.94774149e-002 6.09672546e-001 -3.69134277e-001 + 7.23519564e-001 -1.50532770e+000 + <_> + -4.94053304e-001 -2.03129556e-002 3.51094455e-001 -7.12575167e-002 + 6.09626830e-001 -8.48339379e-001 + <_> + -2.55829871e-001 -2.04114392e-002 6.08850241e-001 -6.66959062e-002 + 3.26420277e-001 -9.34823394e-001 + <_> + -4.29128677e-001 -1.20772831e-001 4.75192070e-002 -5.96620999e-002 + 2.91477859e-001 -7.14823067e-001 + <_> + 3.16006273e-001 -5.89064024e-002 2.85775840e-001 -2.51646817e-001 + -2.09663361e-001 -3.62439960e-001 + <_> + 2.38096714e-001 -2.38081574e-001 4.01368350e-001 -1.85641721e-001 + 4.83360477e-002 -5.61415315e-001 + <_> + 3.85925084e-001 -1.41817957e-001 1.29717022e-001 -2.15761751e-001 + 5.24554290e-002 -6.09167099e-001 + <_> + -2.80800879e-001 -1.67820379e-001 3.04247022e-001 4.60576296e-001 + -6.72610939e-001 8.61088812e-001 + <_> + -7.15438053e-002 -1.01450227e-001 4.59733903e-001 5.09962678e-001 + -3.33105445e-001 5.29044986e-001 + <_> + -1.27664471e+000 -9.46062729e-002 -1.00359595e+000 6.21657193e-001 + -5.17266214e-001 6.28035545e-001 + <_> + -1.44095397e+000 -1.77429453e-001 -1.72627831e+000 1.85915068e-001 + -4.45856690e-001 6.14908278e-001 + <_> + -1.38501894e+000 -5.13777956e-002 -1.65603614e+000 2.09027454e-001 + -1.18958719e-001 3.71635139e-001 + <_> + 2.60071218e-001 6.02763481e-002 -3.23117264e-002 -3.90825778e-001 + 2.55445719e-001 -1.76610792e+000 + <_> + 3.51465106e-001 2.18400937e-002 1.35146633e-001 -9.42007005e-002 + 1.87589675e-002 -6.85183750e-003 + <_> + 5.17772079e-001 4.34821099e-002 2.66585112e-001 -4.98977870e-001 + 4.99863267e-001 -1.19323254e+000 + <_> + -3.14761430e-001 3.83021802e-001 -4.29128498e-001 -6.97424114e-002 + 1.65535420e-001 -1.41128644e-001 + <_> + -3.71695340e-001 3.65488291e-001 -4.04236674e-001 9.14382339e-001 + 3.46421301e-002 1.72882289e-001 + <_> + -2.53832161e-001 1.88292906e-001 -4.21471685e-001 -1.35012940e-001 + -3.63474339e-002 -9.31769386e-002 + <_> + -2.35853046e-001 -6.11988783e-001 1.28842258e+000 -6.12578750e-001 + 1.17704272e+000 -1.05475771e+000 + <_> + -2.49337390e-001 -3.71844590e-001 1.81932676e+000 1.09265280e+000 + 1.47016346e-002 -6.41718414e-003 + <_> + 1.83518324e-002 9.91576687e-002 -3.36208522e-001 6.93848789e-001 + -1.89401899e-002 -7.10980475e-001 + <_> + -1.87409282e-001 9.53468084e-002 5.67503572e-001 9.27700818e-001 + 3.70735437e-001 -1.73016056e-001 + <_> + 1.92649484e-001 5.10672145e-002 -4.18783247e-001 -2.17440743e-002 + 9.22390744e-002 -7.44232982e-002 + <_> + -4.01660562e-001 7.30087832e-002 3.89560282e-001 7.08123744e-001 + 5.78538179e-001 -2.56431729e-001 + <_> + 3.30988884e-001 -2.20289871e-001 2.26762199e+000 1.06062210e+000 + 9.73633647e-001 -8.88065279e-001 + <_> + -3.02775353e-001 -1.84461221e-001 5.94443321e-001 4.54347432e-001 + 1.88140124e-001 -2.85958856e-001 + <_> + 1.23643768e+000 5.78031301e-001 -1.26544237e+000 1.70208490e+000 + 2.28244543e-001 -7.90741861e-001 + <_> + 7.71477520e-001 -7.42300451e-001 1.17082691e+000 2.54161772e-003 + -1.15821815e+000 -1.85041165e+000 + <_> + 1.63247800e+000 3.33129048e+000 -1.15479445e+000 -1.88788831e-001 + 6.26783311e-001 -2.68211460e+000 + <_> + -2.00394213e-001 1.65941000e-001 -2.22819209e+000 1.12944031e+000 + -8.97014141e-001 1.00517964e+000 + <_> + -1.77920312e-001 2.53553241e-001 -2.17597842e+000 7.84048736e-001 + 4.00330685e-002 3.49816471e-001 + <_> + -1.25481188e-001 4.89623636e-001 -1.56847203e+000 1.09847558e+000 + -1.93419230e+000 -4.08649705e-002 + <_> + -9.25194547e-002 3.51419091e-001 -1.91661215e+000 1.36794770e+000 + -2.24179238e-001 -1.32512116e+000 + <_> + -1.36468425e-001 2.91152686e-001 -1.73071158e+000 1.34374011e+000 + 2.58863568e-001 -4.49848056e-001 + <_> + 5.78701317e-001 -1.27015159e-001 6.26192912e-002 -1.84908181e-001 + 3.02692354e-001 -1.56728439e-002 + <_> + 1.49313974e+000 -7.71253288e-001 2.52031183e+000 -1.67401567e-001 + -4.46276754e-001 -7.58838594e-001 + <_> + 1.43970180e+000 -1.06654334e+000 1.08388221e+000 4.68318433e-001 + -2.02270269e+000 -3.94288957e-001 + + <_> + 1568 + -1.3206002681101506e-002 + + 1. 1. 1. 4.2473845754971018e-001 1. 6.8445797050827581e-002 + 2.3111630774630302e-002 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1. 1. 1. 1. 3.5454319599240852e-001 8.5481364696329609e-002 1. + 4.8657671582852025e-001 1. 5.4365773149611318e-001 1. + 7.3268811399946066e-001 5.6834102937213282e-001 + 1.4685111010344554e-001 6.6267007845620973e-001 + 3.5426513417029182e-001 8.6925634478242009e-001 + 5.1417139769394860e-001 3.2929589101012774e-001 + 4.7537729051082100e-001 1. 1.0424885234988748e-001 1. + 5.8061184159680534e-001 4.6248479640558093e-001 1. 1. 1. 1. 1. + 1. 1. 4.2280632573916493e-001 9.1789122860624045e-002 + 6.0250810035510374e-002 1. 1. 1. 1. 1. 1.4475005477675570e-001 + 2.3666913587910701e-001 1. 1. 4.3680201391223134e-001 1. 1. 1. + 1. 1. 1. 1. 1. 1. 1. 4.9937011178659257e-001 1. 1. + 6.7136489812098787e-001 3.0164732413081824e-001 + 4.5451423895569047e-002 8.4253198969234955e-001 + 1.6884442190118110e-001 8.5578230514786191e-001 + 6.1661957230803144e-001 1. 1. 5.5333001183091413e-001 + 1.7709762341641436e-001 1. 1. 1. 6.4737875439344739e-001 + 3.8386368889258904e-001 2.5876700716668788e-001 + 3.8752549786114511e-002 5.2233103859510543e-001 + 5.9617008629746371e-001 4.7841702262672509e-001 + 4.2028319925323282e-001 8.4677979683195261e-003 + 3.2376275588471681e-001 1. 1. 1.0894045406329453e-001 + 2.9719599519580031e-001 3.2791329147148862e-001 + 2.9605622806220859e-002 3.3480385130961343e-001 1. 1. 1. 1. 1. + 1. 1. 1. 9.7153159153443225e-001 3.3545994527256096e-001 + 2.5786013012728731e-001 1.3644082777721808e-001 + 7.2558673825770068e-002 1.5429077738043281e-001 + 1.1315196225233239e-001 1. 2.5903334819002255e-001 1. 1. 1. 1. + 1.0732593305713323e-001 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1. 9.6517240227591705e-001 9.5102054900139643e-001 1. 1. 1. 1. + 1. 1. 1. 6.5785521316347528e-001 6.6536285634970360e-001 + 1.2961499778075353e-001 5.4271708760932125e-002 + 2.7565902229262484e-003 1.9896180880216410e-001 + 2.2542334801534955e-001 7.5464311358435943e-001 1. 1. 1. + 5.2600260735247406e-001 1.8835137172238548e-002 + 1.3203158183848027e-001 2.7957070629469577e-001 + 1.7330494385899001e-001 1.2083864436684788e-003 + 4.5403377232898307e-001 4.1211313738338994e-002 + 1.9987431530215427e-002 1. 8.1484408312134909e-002 + 5.7629587475930466e-001 3.0249366784070336e-001 + 3.3547733262373752e-001 1. 1. 1. 1. 1. 1. 1. + 8.9717361418642527e-001 4.2422619588265414e-001 + 1.3378665480442628e-001 1. 1. 1.5645703265855337e-003 1. 1. + 1.4758315470394584e-001 2.9221731499818715e-001 + 6.7193249786842812e-001 3.4935867419497157e-001 + 2.7029977915127767e-001 3.5466416367570680e-001 1. + 4.5722217736864068e-001 3.7818142752885509e-001 + 2.4769060169540003e-001 2.4429266943249356e-001 + 8.7256237555822069e-002 6.0268723334455310e-001 + 7.1589349200417352e-001 5.7282504932796441e-001 + 5.1534149431211825e-002 1. 4.2964915243419999e-001 + 3.4393630903605465e-001 1.7320773030129047e-001 + 7.4402159508768584e-001 7.3854307630767790e-001 + 6.4152693018981732e-001 5.8451023392834645e-001 + 3.8780003333848234e-001 5.4304605586590571e-001 + 2.9612138735449722e-001 6.6563216795865809e-001 + 2.2556426525395454e-002 1. 1. 1. 1. 1. 1. + 3.3283952677305362e-001 1.8436614652432098e-001 + 3.3225658690689885e-002 1.0543383460877134e-001 + 1.1474274417464440e-001 1.9711211806668399e-001 + 3.0665575738563727e-001 5.6057008315079804e-001 + 6.6805902715580834e-001 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1.4675306126975660e-001 1. 1. 4.3412734272557268e-001 + 4.0005378225678545e-001 1.1250924036988150e-001 + 5.1800111476480441e-002 2.9389481136239742e-001 + 2.6310118472921007e-001 1. 2.0282542877536944e-001 + 1.2929531839549943e-001 5.6810884766018510e-001 + 5.3302049009108687e-001 2.0274803639293448e-001 + 1.2089867834444665e-001 7.8791395260942629e-002 + 4.1340351160919797e-001 6.9857281590028630e-001 + 8.4259894132673363e-001 1. 5.6384996086032102e-001 1. 1. + 3.5379387572022442e-001 1.4420258110815048e-001 + 3.5200278237419591e-001 4.1158056073372068e-001 + 7.9766166564608415e-002 9.8392428669483234e-001 + 1.2646368180246886e-001 3.8520123095059994e-001 + 4.2296607686568305e-001 6.6150607261682620e-002 + 3.0948867346077913e-001 1. 1. 1.9782543597166479e-002 + 1.7481758783505139e-001 5.0842394497628352e-001 1. 1. + 2.2875109462123977e-001 3.1054460770101283e-001 + 8.1783539476703393e-001 1. 1. 4.1440083485086140e-002 + 6.5521358729497339e-002 4.1710215353777946e-001 + 5.2395709078031083e-001 6.3965998799706311e-002 + 7.0515123285088654e-001 5.8882058762874834e-002 + 3.2358787513360276e-001 6.6083509500831611e-002 + 9.1697305904415591e-002 2.9480339207580364e-001 + 5.7102298498437409e-001 1.0807932968130247e-002 + 3.2596758777032681e-001 1. 5.0799346568414512e-002 + 2.8332843766948328e-002 4.2317264641487384e-002 + 5.4809300755960499e-001 1. 5.2031053820859985e-002 + 1.0768701432265104e-001 7.9098865404729449e-002 + 1.0890341274129182e-001 2.6735829995454963e-001 + 2.9426450422661626e-001 1. 3.4815449807951149e-001 + 9.7980272525525902e-001 1. 3.5370037354569478e-001 + 2.7756948885276335e-002 9.0423766026382768e-001 1. + 2.1542138631308064e-001 9.3667994897854989e-002 + 5.2068942819784203e-001 6.4845719898476775e-002 + 1.6127947458963435e-001 1.0366364220638151e-002 + 4.7181160515233556e-001 1. 1. 1. 1. 8.7722832043196197e-001 1. + 1. 1. 1. 1. 7.3287424200456711e-002 1. 1. 1. 1. 1. 1. 1. + 5.0876209900262770e-001 4.3359020651108171e-001 1. 1. 1. 1. 1. + 1. 1. 2.8690590519581927e-001 5.3840884732167060e-001 + 6.7889836635054368e-001 5.8713890994237572e-001 + 6.2798152742641333e-001 5.3170927129548595e-001 + 3.9524693058836907e-001 2.3746689186539274e-001 + 3.9243901415248228e-001 8.1960748498613545e-001 + 9.8240456556990963e-001 8.5602206856534802e-001 1. 1. 1. 1. 1. + 1. 1. 2.9696117875465000e-001 1.7023712449709028e-001 + 4.1072062394481779e-001 1. 2.4381197553711759e-001 + 9.3502570813255992e-001 2.1305992081944974e-001 + 2.9640233132164973e-001 6.5906415502287874e-001 + 8.5461135292669110e-001 1.2624100781217742e-001 1. + 4.9274944674928089e-001 1.6984468145935772e-001 + 9.2326207851717815e-002 2.8221240832184752e-001 + 4.2415995162704723e-002 3.2269522983133953e-001 + 4.4595515887713622e-001 6.9864665042995266e-001 + 5.7471978993028017e-001 9.4013964947542092e-002 + 5.1749885781748661e-001 3.8200394730526804e-001 + 1.5630828839313035e-001 5.6991786813715639e-002 + 2.4145731269618953e-001 1. 1. 2.7979709531538471e-001 1. + 8.8413963442431887e-001 6.8952056817854657e-001 + 4.7518050879968032e-001 8.6450382957399555e-002 1. 1. + 5.8179682692776413e-001 4.4396504558933420e-003 + 4.0972102702730906e-001 8.0033334349663532e-001 1. 1. + 7.0961332558595436e-001 7.8442431462283513e-001 + 3.6340015451652679e-004 1. 1. 1.3620078897327241e-002 + 9.3619437425934468e-002 2.1629154651958768e-001 + 1.1098718995828746e-001 3.1564581876942960e-001 + 3.2748787050104056e-001 1.9362422897877166e-001 1. 1. 1. 1. 1. + 4.5059409544428786e-001 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1. 1. 1. 2.6125475886472382e-001 4.7809173558959867e-001 1. 1. + 9.9171167045221398e-001 1. 1. 3.5137440413890197e-001 1. + 5.8727782294898867e-001 1. 1. 1. 1. 8.9586766415578542e-003 1. + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 2.3313210985436389e-001 1. + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 2.3827256231015040e-001 3.3939163735490641e-001 1. + 7.0589886271658486e-001 1. 1. 1.3951737548908077e-001 + 4.6337972436696662e-001 6.3370495092736526e-002 1. 1. 1. 1. 1. + 1. 1. 8.3647637270582731e-001 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1. 1. 1. 1. 1. 5.4716861719593536e-002 8.0051553457069335e-001 + 1. 2.3231805256268803e-001 1. 1. 4.5830277017570975e-001 1. 1. + 1.0235115746187837e-001 2.1292171784977251e-001 + 9.0973409052252596e-001 4.4340543658073511e-001 + 3.0347812815721925e-001 9.3335616708505445e-002 1. 1. 1. 1. 1. + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 5.1473493925113689e-001 1. 1. 6.9198186131464223e-001 1. 1. 1. + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 5.2325259577513417e-001 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1. 1. 1. 1. 7.0903023932636200e-002 1. 1. 1. 1. 1. + 2.6610287324306803e-001 2.3536923398830781e-001 + 8.4006248021583255e-002 1.2982832760927432e-001 1. + 4.1049660290813711e-001 5.7380046066002410e-001 + 2.3422094645861982e-001 4.0910148207125387e-002 1. 1. 1. 1. 1. + 1. 1. 1. 1. 2.0171904311471819e-001 1. 1. + 5.4597831195046997e-001 9.3907921305052822e-001 1. 1. 1. 1. 1. + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1. 1. 4.7913534742066982e-001 8.3407398361076590e-001 1. 1. 1. + 1. 1. 1. 1. 1. 1. 3.1634748756173492e-001 1. + 3.2029869053198740e-002 1. 1. 1. 2.9025819731499802e-001 + 5.4458106915076976e-001 1.4823827441065096e-001 + 3.6830398680340892e-001 6.4805935637181244e-005 1. 1. + 4.3597492442172464e-001 5.8743854739748258e-001 1. 1. 1. + 5.3558118768611329e-001 8.5769464895377379e-001 + 1.0447974867554301e-002 6.5368108325927965e-001 1. 1. 1. + 7.5107507955304342e-001 1. 1. 1. 1. 2.9998189644305506e-001 + 2.9855678770846783e-001 1. 1. 7.8372293342660926e-001 1. 1. 1. + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 3.6245015385374263e-001 1. 1. 1. 1. 1. 1. 1. + 4.1221528133400021e-001 1. 1. 1. 1. 9.6490118826794702e-002 1. + 1. 1. 1. 2.1556614963015339e-001 2.0975067916901796e-001 + 7.4545472733185392e-001 1.4719443987279984e-001 + 4.8670827673182021e-001 1. 1.4812675377899362e-001 1. + 3.6437775006938478e-001 1.9267928470760060e-001 + 1.1446904393467527e-001 1. 2.0729564921336102e-001 + 2.9853188069466469e-001 3.5844412100756640e-001 + 4.5759271763168213e-001 9.2840282071048963e-001 + 5.3323906397050025e-001 -1. -1. -1. -3.6507004551261524e-001 -1. + -1. -1.2198703315277562e-001 -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -7.5059848960721676e-001 -1. -1. -1. -1. -1. + -1. -1.0569001379586079e-001 -5.8870337556585584e-001 + -4.8294298418643972e-001 -1.9707662485868707e-001 -1. -1. -1. + -1. -3.9360169654501898e-001 -1. -1. -1. -1. + -3.6090847441963773e-002 -8.1644751542701943e-001 + -6.0826993038224164e-001 -1. -1. -1. -1. + -8.4231792587223564e-001 -1. -1. -1. -2.2822072050457631e-001 + -5.7400964356875173e-001 -3.3892573820297983e-001 -1. + -7.1904667896228680e-001 -1. -8.0771867094179040e-001 + -2.6677889751155839e-002 -4.0577199612063158e-001 + -7.8690951629722650e-001 -1. -6.8413312137779958e-001 + -2.5977717172362158e-001 -3.3208527768553464e-001 + -1.2957934681869612e-001 -4.3550322933237423e-001 + -3.6477484507226299e-001 -1. -1.2902220818375462e-001 + -6.8715225586039252e-001 -2.4856441970007717e-001 + -3.1326174269893331e-001 -3.7083668005535120e-001 + -4.7989124995722793e-001 -2.5903922866366591e-001 -1. -1. -1. + -1. -1. -1. -1. -1. -1. -9.3614182172285620e-002 + -3.1497577715835744e-002 -2.3566463452923281e-001 -1. + -9.8349733277898879e-002 -1.3362323816665861e-001 + -4.5897578452593712e-001 -1.3982749535964828e-001 + -1.0291837942069591e-002 -1. -1. -1. -1. -1. -1. -1. + -4.2642319475907381e-001 -1. -7.8653142774751028e-001 -1. -1. + -1. -1. -1. -6.5608129224509715e-001 -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -3.2602331841091620e-001 -5.5075157093878413e-001 + -3.1202583769625214e-001 -5.6408499287246527e-001 + -8.6563973334630095e-001 -8.1589759192047306e-002 -1. -1. -1. + -1. -1. -1. -1. -1.8542146661366452e-001 + -2.7267176580556979e-001 -6.4719525341100884e-002 + -2.1415885304343110e-001 -3.8219777847434183e-001 -1. -1. -1. + -8.1687634795438571e-002 -3.3422987190487141e-001 + -2.5594694376941468e-001 -1.0863793604394795e-001 + -8.3048284047714516e-001 -6.7023105697658258e-001 + -5.6234132633928191e-001 -1. -3.4430786838254834e-001 + -5.3597128561126217e-001 -5.0886468862810119e-001 + -4.1466169011709825e-001 -2.7569204299184268e-002 + -3.9662042962296790e-001 -7.0018429958801609e-001 + -9.3588623509435004e-002 -1. -1.9047503051093598e-001 -1. + -3.1685277313131788e-001 -4.4120386087557190e-001 + -6.4173376671704518e-001 -8.0721142799037915e-001 + -1.9568754539438479e-001 -1. -1. -1. -4.7786090720163615e-001 + -7.2905980690673611e-001 -9.2842338448583278e-002 + -1.3473229091582806e-001 -4.1769138772874798e-001 -1. + -9.1371419242699814e-001 -5.1703621837948383e-002 + -1.8607257095080876e-001 -4.7644613691115340e-001 + -6.2898303368823338e-001 -4.8164600105449901e-001 + -3.9602896674130678e-001 -4.3708846197281694e-002 -1. -1. -1. + -1. -1. -4.0735316032494934e-001 -1. -1.7198669738646688e-001 + -9.5403745650916930e-002 -8.7965587261787415e-001 + -8.1649444658158904e-002 -1.5233329700093279e-002 + -2.9055729090237115e-001 -1. -1. -1. -9.9867899948076910e-001 + -6.4635461784280690e-001 -1.4060514636606988e-001 + -5.1120168594721377e-002 -5.2880374072111980e-001 + -4.7899146782603541e-001 -2.9103819269530345e-001 + -4.1981959182450845e-002 -8.8352998910261549e-001 + -2.1660435995989580e-001 -7.1702224881085241e-002 -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -7.2625506190005529e-001 -1. -5.1167008359747213e-001 + -4.4578750047684212e-001 -1.5467844299085567e-001 + -3.7840707379506824e-003 -3.1902288400822637e-001 + -7.9108171378757186e-001 -8.7146779570414867e-001 + -2.2142578870621499e-001 -2.1772923925658411e-002 + -1.2241443968642444e-001 -5.2684313054627052e-001 + -5.4490622516737175e-001 -1. -1. -1. -1. + -5.7843947930785566e-002 -1. -1. -1. -2.0813459500715867e-001 + -6.0295487722244401e-001 -4.7251083443652825e-001 + -5.4346857389908509e-001 -2.9847355659649871e-001 + -3.8131694834225294e-001 -1.8932518976447688e-001 -1. + -5.7960739412399993e-002 -3.1093023868814113e-001 + -1.1644889987997732e-001 -5.4975564376885888e-001 + -2.2801487665938530e-001 -1. -1.4366878203722477e-001 + -7.2759244984748428e-001 -5.3618434098219503e-001 + -3.0622117998388532e-001 -6.5861778544186178e-001 + -8.8950505668997093e-001 -8.9161685123517165e-001 + -9.3637839554446961e-002 -1. -2.4858344907090218e-001 -1. + -2.5164872529905465e-002 -2.8051351595622148e-001 + -1.8487192837943867e-001 -2.4794699626819816e-001 + -3.5068047995315965e-001 -1.3132106673433866e-001 + -2.8119846105612478e-001 -2.6057917066862479e-001 -1. + -1.9593700179564089e-001 -7.4845035236211965e-001 -1. -1. + -2.9965107712077954e-001 -3.5171283066696929e-001 + -7.2617903953894725e-001 -1.2276236377751602e-001 + -4.3488490295563365e-001 -5.9036184336992481e-001 + -3.0498328874353109e-001 -1. -4.5757292341313477e-001 -1. + -5.0992884680956441e-001 -3.4409526843301302e-001 + -4.5488087096902369e-001 -7.6197943510265487e-001 + -5.2194192065516443e-001 -1.8744455131405335e-001 + -7.7660233566320824e-001 -9.3870686369446815e-002 + -1.2253079457650366e-001 -4.3557872875527454e-001 + -4.3421140881695691e-001 -7.2210187733580800e-001 + -4.8233390129253417e-001 -1. -1. -1. -1. -1. -1. -1. -1. + -4.9227572510562601e-001 -1. -1. -1. -1. + -1.6341509540327942e-001 -1. -9.2323259400804369e-001 -1. -1. + -1. -3.6142158276791814e-001 -3.3760126219655356e-001 + -7.5508529759846610e-001 -3.3331873543879958e-001 + -8.6196475892059154e-001 -8.1631359566151629e-001 + -4.3787320970518812e-001 -4.5857980513948216e-001 + -5.6543424728655212e-001 -8.8191582633106413e-001 + -8.6680637981478470e-001 -8.5392655790403382e-001 -1. -1. -1. + -7.3367699532934527e-001 -2.1086467664458461e-001 -1. + -4.8745815810184467e-001 -1.3248440758346730e-001 + -6.1213202384311194e-001 -1.1841390858154167e-001 -1. + -2.1303173296582242e-001 -2.3183786267345929e-001 + -3.6249699502757221e-001 -1. -1.1919392782221584e-001 + -6.1534314875394638e-002 -8.7229818157480199e-001 + -1.4539586350811665e-001 -2.3141435117384976e-001 -1. -1. + -1.1909816295830356e-001 -1. -9.5505332369352602e-002 -1. + -1.6651088017133520e-001 -9.1202635199129620e-002 + -1.0034507881354834e-001 -1.1852354396239634e-001 -1. + -5.4523234226666065e-002 -5.5975644486005294e-001 + -1.0518863033352945e-002 -3.4188373309992570e-001 + -3.6053346029152772e-001 -3.2773086887949615e-001 + -2.2154678707311104e-001 -1. -1.1662301410435941e-001 -1. + -3.3352336213216138e-001 -1. -4.8568083362965572e-002 + -4.1402234868543780e-001 -2.8487659688332220e-002 + -2.6686615104455556e-002 -3.8344998539105357e-001 + -6.0621625338013652e-001 -9.8935679750505001e-001 -1. -1. + -8.5877744497092123e-001 -5.8501128287977466e-001 -1. -1. -1. + -1. -1.5343287480659901e-001 -1.4202018124391799e-001 + -6.1456126196551546e-002 -1. -1. -4.6127263345509995e-001 + -4.5156988265795350e-001 -1. -3.3119231433111529e-001 -1. + -1.2123912692725938e-002 -1. -6.7115458050410715e-001 -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -6.2786058782618981e-001 -1. -3.2766618031955613e-001 + -3.0664755617180867e-001 -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -1. -9.8826562718305655e-001 + -8.0391290459062736e-001 -1. -1. -1. -1. + -8.9442936825640043e-002 -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -2.0849284791385503e-001 -1. -1. -1. -1. -1. -1. -1. + -4.7341751239572594e-001 -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -4.2471951336722158e-002 -9.3390624367030786e-003 + -1. -1. -1. -1. -6.9311348111002047e-001 -1. -1. -1. -1. -1. + -2.9290563048620860e-001 -3.9586534079405594e-001 -1. -1. -1. + -1. -1. -1. -7.4301765458830416e-001 -3.3729771330524383e-001 + -2.6803977495177816e-001 -3.4361485246653434e-001 -1. -1. -1. + -1. -1. -1. -1. -1. -1. -6.2321630035924142e-002 + -6.7331485136694058e-001 -1. -1. -1. -1. -1. -1. + -6.6272184349728125e-001 -1. -1. -1. -1. + -8.5765420314330643e-001 -1. -8.5077795328588057e-001 -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -8.8739480161475737e-001 -1. -1. -1. -1. -1. + -1.3278891994660316e-001 -2.5779365083175154e-001 + -7.7763587707845339e-001 -1. -5.1024060747300737e-001 -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -4.6860086374804327e-001 -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1.5416266735949485e-001 -1. + -1. -1. -1. -1. -1. -1. -1. -1. -3.9233059704652079e-001 + -3.2785279424840386e-001 -1. -1. -1. -1. + -3.1668051963018934e-001 -1.6916456642087272e-002 + -3.9954512458512479e-003 -6.5714642946946522e-001 + -8.6985555158749384e-001 -1. -1. -1. -1. -1. + -2.2834761300661449e-001 -2.9934043778719038e-001 + -4.0852158094056795e-001 -7.2261558901757605e-001 + -4.6744998306177188e-001 -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -5.9097233015293393e-001 -3.3798671485916792e-001 + -5.6890690577087466e-001 -4.4272762095843088e-001 -1. -1. -1. + -1. -1. -3.4636404442968528e-003 -2.3306161157770255e-001 + -1.0693760796434031e-001 -1. -1. -1. -1. -1. + -2.8732585421541161e-002 -1. -1. -1. -1.8194431970500100e-001 + -8.0326930821064568e-002 -1. -2.7206423949930753e-001 -1. -1. + -1. -1. -8.8220108914404127e-001 -5.6319622011031134e-001 -1. + -1. -1. -1. -6.3458336770303925e-001 -1. + -5.5017654065106655e-002 -1. -1.6532666848225921e-001 -1. + -6.5025027073582309e-001 -1. -1. -1. -4.0681372505032863e-002 + -2.3139967142701178e-002 -1. -1. -1. -4.2725212753349856e-001 + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1.8874512313468209e-001 -1. -1. -1. -1. + -6.5720095449580984e-001 -1. -8.3548215991666852e-001 + -3.6500122176304506e-001 -5.8944248633648177e-001 + -8.7120888911019534e-001 -7.5353898388709284e-001 + -9.7437092387080626e-001 -2.7941791371453462e-001 -1. -1. + -4.7881535275819051e-002 -9.9164765649381925e-002 + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 + 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 + 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 + 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 + 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 + 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 + 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 + 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 + 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 + 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 + 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 + 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 + 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 + 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 + 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 + 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 + 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 + 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 + 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 + 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 + 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 + 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 + 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 + 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 + 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 + 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 + 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 + 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 + 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 + 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 + 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 + 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 + 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 + 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 + 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 + 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 + 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 + 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 + 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 + 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 + 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 + 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 + 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 + 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 + 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 + 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 + 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 + 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 + 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 + 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 + 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 + 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 + 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 + 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 + 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 + 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 + 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 + 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 + 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 + 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 + 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 + 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 + 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 + 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 + 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 + 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 + 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 + 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 + 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 + 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 + 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 + 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 + 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 + 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 + 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 + 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 + 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 + 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 + 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 + 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 + 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 + 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 + 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 + 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 + 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 + 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 + 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 + 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 + 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 + 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 + 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 + 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 + 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 + 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 + 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 + 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 + 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 + 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 + 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 + 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 + 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 + 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 + 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 + 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 + 1561 1562 1563 1564 1565 1566 1567
+
diff --git a/acii/acii-client/trainedClassifiers/SVM_UD_N00006.xml b/acii/acii-client/trainedClassifiers/SVM_UD_N00006.xml new file mode 100755 index 0000000..f5be115 --- /dev/null +++ b/acii/acii-client/trainedClassifiers/SVM_UD_N00006.xml @@ -0,0 +1,4597 @@ + + + + C_SVC + RBF + 1.3000000000000000e+000 + 1. + 2.2204460492503131e-016 + 100000 + 6 + 6 + 2 + + 1 + 2 +
i
+ + -1 1
+ 1308 + + <_> + -2.05998138e-001 -5.68323359e-002 -3.26263070e-001 + -3.79009306e-001 -4.04858679e-001 3.58362019e-001 + <_> + -7.76316881e-001 -1.41397521e-001 2.80329406e-001 -1.74036682e-001 + -4.58594739e-001 3.43215287e-001 + <_> + -1.66840923e+000 2.38819122e-001 3.46769392e-001 -2.90406823e-001 + 5.92467189e-001 -3.95476878e-001 + <_> + -1.36903489e+000 -5.33293746e-002 -1.06843519e+000 3.94983739e-001 + 1.89148292e-001 -2.08523780e-001 + <_> + -1.61025739e+000 -4.95008193e-002 -1.06681436e-002 5.04660487e-001 + 5.99431455e-001 -3.04857224e-001 + <_> + -1.59948850e+000 -6.80521578e-002 -9.04260516e-001 1.76950227e-002 + -1.77218735e-001 -5.79164326e-002 + <_> + -1.64256394e+000 2.71325022e-001 8.67181718e-001 -1.08287767e-001 + 4.62807506e-001 -3.38991135e-001 + <_> + -1.59991920e+000 2.22014129e-001 8.97836089e-001 1.27117649e-001 + 7.56444335e-001 -6.22230053e-001 + <_> + 3.25983495e-001 2.02867553e-001 -5.75265110e-001 -1.05875051e+000 + 2.11158347e+000 -1.53262949e+000 + <_> + -2.37890426e-002 -7.17471167e-002 -5.98270357e-001 + -9.55031395e-001 6.86538398e-001 -7.31404364e-001 + <_> + 2.14848861e-001 8.48557130e-002 -7.99214125e-001 -5.77849448e-001 + -1.56730011e-001 -6.63177818e-002 + <_> + 1.52649581e+000 -3.31169754e-001 6.69566393e-001 5.13226032e-001 + -2.37120584e-001 -7.40590096e-002 + <_> + 6.15019679e-001 -3.09340451e-002 -7.42973089e-001 -1.59722579e+000 + 3.83303344e-001 -4.66145545e-001 + <_> + 1.42225325e+000 -1.83246449e-001 3.37337703e-001 -7.31993437e-001 + -2.63740301e-001 5.84332757e-002 + <_> + 9.09224808e-001 5.35291061e-002 -6.67808056e-001 -1.81519520e+000 + 3.27550024e-001 -2.80005932e-001 + <_> + 1.28613484e+000 1.71280921e-001 5.74742019e-001 -9.38332975e-001 + 8.97334591e-002 -3.21903855e-001 + <_> + 1.17973852e+000 -7.25182444e-002 3.82491499e-001 -6.99245334e-001 + -2.76857316e-001 2.13690445e-001 + <_> + 1.21376812e+000 -9.96949524e-002 3.69803458e-001 -6.21339500e-001 + -2.56626874e-001 1.02381371e-001 + <_> + 6.25357747e-001 6.82872459e-002 -5.13186216e-001 -7.71821976e-001 + 2.41263792e-001 -3.79579782e-001 + <_> + -1.49138555e-001 1.98969185e-001 -2.01620698e-001 2.70117223e-001 + -5.33763990e-002 7.34443963e-002 + <_> + -1.77999094e-001 7.04155639e-002 1.13054134e-001 4.50073808e-001 + -2.53601968e-001 2.61325985e-001 + <_> + -1.74983814e-001 -8.74415264e-002 -1.85393587e-001 + -9.89395604e-002 1.16668296e+000 -1.11552060e+000 + <_> + -1.66799486e-001 -1.02173701e-001 1.73944697e-001 1.47811294e-001 + 1.01237309e+000 -9.68367040e-001 + <_> + -5.26660541e-003 3.66302580e-003 -1.26698520e-002 7.36840116e-003 + 1.10014343e+000 -1.19847465e+000 + <_> + 2.06664532e-001 4.63647507e-002 -2.75499284e-001 -9.83212516e-002 + 7.93490887e-001 -2.88938023e-002 + <_> + 1.92880392e-001 -3.12785199e-003 -6.00676388e-002 -6.02319725e-002 + 1.32166588e+000 -1.01595569e+000 + <_> + -7.89256021e-002 8.24539438e-002 -4.22924787e-001 -4.98542413e-002 + 6.63040876e-001 -7.07642019e-001 + <_> + -1.03864634e+000 9.17339385e-001 -2.94689083e+000 -1.24721050e+000 + -1.10250831e+000 7.70001948e-001 + <_> + -1.57924306e+000 1.76422822e+000 -8.55527282e-001 -1.38040459e+000 + 7.10168481e-001 -7.40486383e-001 + <_> + -1.17993379e+000 1.22722363e+000 -2.08594894e+000 -9.79698837e-001 + -1.42039573e+000 1.23614955e+000 + <_> + -1.62576449e+000 2.25997114e+000 -2.01866388e+000 -3.46030056e-001 + -4.69843261e-002 -7.62633756e-002 + <_> + -4.57558662e-001 9.53399122e-001 -2.74447608e+000 -7.53496528e-001 + -1.83114931e-002 5.00448011e-002 + <_> + -1.01064730e+000 2.29681349e+000 -2.31895471e+000 -4.90734540e-003 + 3.90142808e-003 1.36462241e-001 + <_> + 9.80979670e-003 2.10422844e-001 3.48935515e-001 9.40782726e-002 + -1.63666558e+000 1.10462976e+000 + <_> + -6.33737206e-001 6.06640220e-001 2.92007446e+000 -5.68623722e-001 + -5.73288560e-001 4.52737600e-001 + <_> + 4.34533596e-001 6.22923613e-001 3.91106933e-001 -1.19761455e+000 + -4.86154974e-001 2.26370841e-001 + <_> + 8.00674796e-001 4.47803706e-001 -2.12113798e-001 -1.76323593e-001 + -1.44530296e+000 7.24068522e-001 + <_> + 4.68563199e-001 2.22900200e+000 -7.21453249e-001 1.52239871e+000 + -8.66178513e-001 1.46572936e+000 + <_> + 6.34403586e-001 1.29023111e+000 9.08017695e-001 -3.54968727e-001 + -4.69046324e-001 2.14641765e-001 + <_> + 3.17368418e-001 2.20857191e+000 -9.37666535e-001 1.05396235e+000 + -9.87490118e-001 8.01653981e-001 + <_> + -1.22731674e+000 -2.10498810e+000 3.97195697e-001 1.17019928e+000 + 2.64535928e+000 -1.99412906e+000 + <_> + -1.83984935e+000 -1.72121251e+000 1.66609967e+000 9.93827999e-001 + 2.69242358e+000 -2.25140309e+000 + <_> + -1.43278658e+000 -5.63056707e-001 1.20881033e+000 3.78436148e-001 + 2.71786880e+000 -2.12409949e+000 + <_> + -1.63653338e+000 -1.04075909e+000 1.35012400e+000 1.05103898e+000 + 2.28169274e+000 -1.94456565e+000 + <_> + -1.73689914e+000 -9.73959029e-001 7.65054226e-001 4.71767038e-001 + 2.09637380e+000 -1.68196154e+000 + <_> + -2.02982843e-001 3.64132255e-001 2.52824736e+000 1.05960739e+000 + 2.35745311e+000 -9.63847160e-001 + <_> + -1.57753646e-001 1.87270522e+000 2.27005005e+000 1.19527555e+000 + 1.42730355e+000 -7.53119588e-001 + <_> + -1.21094811e+000 -2.03656673e+000 -9.20686662e-001 2.66415858e+000 + -6.38450563e-001 1.49786484e+000 + <_> + -1.87689424e+000 -1.62414753e+000 1.89773768e-001 1.02189541e+000 + 7.69038498e-001 -3.04940641e-001 + <_> + -1.88852465e+000 -1.51259625e+000 -5.06780207e-001 1.40387738e+000 + 8.60787451e-001 -5.00459075e-001 + <_> + -1.71277690e+000 2.18090028e-001 -4.77579474e-001 -3.82215708e-001 + 1.24962878e+000 -8.85002494e-001 + <_> + -2.38735452e-001 1.04976487e+000 -1.44503319e+000 -1.80587578e+000 + -1.45642459e+000 6.35778964e-001 + <_> + -9.24065650e-001 9.01286960e-001 -1.02745211e+000 -6.66808188e-001 + -2.14121556e+000 3.36657095e+000 + <_> + -1.90921724e-001 9.92132187e-001 -4.61017817e-001 -2.03274012e+000 + -7.12202907e-001 2.92842537e-001 + <_> + -2.86549181e-001 1.09896159e+000 3.87314081e-001 -1.38464868e+000 + -1.50338280e+000 8.22795689e-001 + <_> + -6.00569069e-001 1.31160462e+000 -2.12592587e-001 -2.16501904e+000 + -1.44880807e+000 8.23609650e-001 + <_> + -1.09162915e+000 5.99085152e-001 -2.93449140e+000 -1.20427978e+000 + -1.79918849e+000 8.34483027e-001 + <_> + -7.60378957e-001 8.49324584e-001 -3.82832944e-001 -1.52428377e+000 + -2.18917203e+000 1.06744242e+000 + <_> + -1.16614962e+000 7.52536595e-001 -8.79680455e-001 -6.89444244e-001 + -2.55258131e+000 2.80963659e+000 + <_> + -5.49309313e-001 1.54998064e+000 1.04937172e+000 -1.55273247e+000 + -6.69229269e-001 4.33099233e-002 + <_> + -4.11898702e-001 1.57085145e+000 1.78037584e+000 -1.41702366e+000 + -6.96880043e-001 -1.37553066e-002 + <_> + -1.13556600e+000 2.89079487e-001 -1.06414914e+000 -2.37657714e+000 + 2.58053482e-001 -4.50653404e-001 + <_> + -1.87044933e-001 1.92152214e+000 -4.73702967e-001 -1.48731518e+000 + 1.05077183e+000 -8.75783145e-001 + <_> + 4.06965315e-001 1.97587833e-001 -8.92717540e-001 -1.62440193e+000 + -4.03047889e-001 2.30362695e-002 + <_> + -5.79892874e-001 1.85714662e+000 1.64997593e-001 -6.30966246e-001 + 1.14491403e+000 -7.01649368e-001 + <_> + -1.33500528e+000 1.63573992e+000 -9.06475663e-001 -6.61231458e-001 + 1.44399548e+000 -9.19391513e-001 + <_> + -3.57192904e-001 1.04789659e-002 -2.35515833e+000 -2.08305907e+000 + 5.49298167e-001 -6.31557703e-001 + <_> + -3.83468896e-001 3.36621189e-004 -1.83769965e+000 -3.26154947e+000 + 5.97996473e-001 -1.07255948e+000 + <_> + -2.12028697e-001 -2.92127710e-002 -1.71989012e+000 + -2.53571987e+000 1.14685845e+000 -1.36698401e+000 + <_> + -3.67116742e-002 8.50266740e-002 2.22118363e-001 -8.43853951e-001 + 1.51861101e-001 -2.86564887e-001 + <_> + -1.43538743e-001 -1.05149232e-001 9.20766354e-001 -3.61302316e-001 + -3.79172385e-001 1.10821903e-001 + <_> + -1.67142451e+000 1.69633493e-001 -3.42446566e+000 -4.53338087e-001 + 6.15025938e-001 -8.49169314e-001 + <_> + -1.43192506e+000 1.44204021e-001 -1.02060759e+000 6.57670200e-002 + -1.16736317e+000 9.38555479e-001 + <_> + 9.39377606e-001 -1.40492797e-001 5.02683401e-001 -8.37218463e-001 + -1.36618340e+000 5.87002158e-001 + <_> + -1.18124247e-001 -1.29637286e-001 3.18376273e-001 -5.07692158e-001 + -4.67696667e-001 -2.10041940e-001 + <_> + -9.98586178e-001 -1.03250727e-001 -9.52203453e-001 + -1.12739491e+000 -4.77882266e-001 3.97389531e-001 + <_> + -9.78787914e-002 -4.23346721e-002 -2.90936083e-001 + -1.51421106e+000 -5.37591219e-001 -1.06358781e-001 + <_> + -1.18985750e-001 1.08245045e-001 1.23185313e+000 -2.23575339e-001 + -1.32391155e+000 7.29471862e-001 + <_> + -2.63719201e-001 -1.03696711e-001 -4.03279781e-001 + -3.27565193e-001 -7.27514625e-002 -7.15939701e-002 + <_> + -1.37205017e+000 1.34221226e-001 -9.23810363e-001 -8.75016987e-001 + 2.80385703e-001 -5.04970074e-001 + <_> + -1.66496313e+000 2.05347642e-001 -2.34755591e-001 -2.03766751e+000 + -2.32277028e-002 -4.08184886e-001 + <_> + 6.10695630e-002 -5.39046973e-002 2.73747778e+000 -6.39551520e-001 + -8.91449094e-001 6.17531955e-001 + <_> + -4.08883423e-001 -5.71966395e-002 8.21428001e-001 -5.46490066e-002 + 1.64293337e+000 -7.61840641e-001 + <_> + -2.38735452e-001 4.26690817e-001 2.00953335e-001 6.69389427e-001 + 1.27803957e+000 -2.72214204e-001 + <_> + -1.09509155e-001 3.52008611e-001 9.69762027e-001 -1.67482451e-001 + 1.96490991e+000 -1.70632339e+000 + <_> + -3.64515722e-001 4.65417862e-001 5.20303607e-001 -3.37222785e-001 + 2.46117210e+000 -2.00813174e+000 + <_> + -5.87646484e-001 1.30859351e+000 -2.08704546e-001 7.48766541e-001 + 2.05139351e+000 -9.60367858e-001 + <_> + -1.08904457e+000 1.44465387e-001 1.73825634e+000 -1.37373424e+000 + -6.59817517e-001 4.92976993e-001 + <_> + -4.88572955e-001 5.48827946e-001 2.38360238e+000 -2.37451911e+000 + -4.68312442e-001 -2.53331572e-001 + <_> + -7.08688438e-001 -5.48385084e-002 1.48488736e+000 -1.89367354e+000 + -7.34559596e-001 5.37571788e-001 + <_> + -1.67271674e+000 1.91518867e+000 7.15934515e-001 -8.81629050e-001 + 1.31937039e+000 -1.21018374e+000 + <_> + -5.56632161e-001 3.99813741e-001 -2.44296879e-001 -2.88970423e+000 + 4.38599080e-001 -4.44512457e-001 + <_> + -2.86549181e-001 1.67986155e+000 3.90988678e-001 -5.97891271e-001 + 1.43748891e+000 -1.23843145e+000 + <_> + -6.82843149e-001 4.18710142e-001 -1.88355774e-001 -2.62265253e+000 + 8.47423613e-001 -7.44831264e-001 + <_> + -1.74077594e+000 1.20065176e+000 -3.77909422e-001 -2.12893248e+000 + -3.98501903e-001 -1.39455676e-001 + <_> + -1.73603761e+000 1.17308187e+000 -2.37934098e-001 -3.71767235e+000 + -4.14114416e-001 -1.26088873e-001 + <_> + -1.39789546e+000 8.77176642e-001 1.43284595e+000 -1.66668403e+000 + -3.94051671e-001 -1.36269704e-001 + <_> + -9.81786728e-001 5.66413589e-002 1.37931323e+000 -9.74436581e-001 + 5.48182309e-001 9.53494161e-002 + <_> + -3.74853820e-001 1.11908458e-001 1.01167679e+000 -1.04398274e+000 + 1.27303469e+000 -1.24939919e+000 + <_> + -1.03132355e+000 -6.70028776e-002 2.10185990e-001 3.49228144e-001 + 3.04952353e-001 -9.60405171e-002 + <_> + -9.38280582e-001 1.32583186e-001 1.75623703e+000 -3.32372487e-001 + 2.19203925e+000 -1.68883753e+000 + <_> + -1.00375521e+000 -1.43414393e-001 3.93668205e-001 5.36387324e-001 + 5.44531345e-001 -6.82977736e-002 + <_> + -1.80926585e+000 4.45559919e-001 -4.72889602e-001 -4.18965429e-001 + 1.03256442e-001 3.88629049e-001 + <_> + -1.88723242e+000 1.30153620e+000 -2.47435004e-001 -1.98495758e+000 + 1.45133817e+000 -1.32828045e+000 + <_> + -1.70717704e+000 1.24277306e+000 -4.26533043e-001 -7.72592485e-001 + 2.74460745e+000 -2.11962128e+000 + <_> + -1.85751033e+000 8.79008651e-001 -3.71287078e-001 -8.58316898e-001 + 8.79635274e-001 -9.35868800e-001 + <_> + -8.06486160e-002 6.02004901e-002 -1.07690409e-001 -6.12097383e-001 + 4.22938317e-001 2.30114296e-001 + <_> + -4.79527116e-001 7.14635730e-001 -2.72024226e+000 -1.77449524e-001 + -7.03611076e-001 2.06828013e-001 + <_> + -6.77243352e-001 7.40316749e-001 -2.55482721e+000 -2.02849531e+000 + -3.69850546e-001 5.50411679e-002 + <_> + -9.56372261e-001 1.41404676e+000 -2.68276930e+000 -8.10792089e-001 + -8.66433322e-001 6.52983427e-001 + <_> + -9.62402821e-001 1.50507581e+000 -1.08359560e-001 -7.00449422e-002 + -1.08893538e+000 1.39410412e+000 + <_> + -4.47220564e-001 6.84721589e-001 6.63128674e-001 8.13340489e-003 + -2.11709642e+000 2.60166144e+000 + <_> + -1.07009137e+000 7.17647970e-001 -4.29503888e-001 -1.97952533e+000 + 1.42919457e+000 -1.52374840e+000 + <_> + -1.15581155e+000 -3.60895805e-002 1.17986292e-001 -1.92405004e-002 + 1.59940314e+000 -1.40174365e+000 + <_> + -9.71879423e-001 1.75507352e-001 -6.36124536e-002 -2.84469426e-001 + 1.62467790e+000 -1.87721455e+000 + <_> + -1.34146667e+000 3.54434341e-001 -3.92365575e-001 -5.50850928e-001 + 1.81153965e+000 -1.93126166e+000 + <_> + -5.73000848e-001 3.08764458e-001 1.90558299e-001 -1.69646192e+000 + 7.28217661e-001 -1.41849351e+000 + <_> + -6.55722171e-002 1.00856692e-001 -8.82091761e-001 9.00712311e-001 + -1.26567614e+000 1.58478773e+000 + <_> + -8.23716298e-002 -2.87503332e-001 6.53163373e-001 1.15614688e+000 + -2.61744887e-001 2.92025304e+000 + <_> + -1.21570282e-001 4.72588032e-001 3.57089430e-001 9.77819562e-001 + -1.51905310e+000 2.08385682e+000 + <_> + -2.63719201e-001 -3.93956572e-001 9.36650217e-001 8.53142500e-001 + 3.18231195e-001 6.54511094e-001 + <_> + -2.74488062e-001 -5.78612499e-002 1.17250276e+000 7.72432089e-001 + 1.14488757e+000 -8.66867974e-002 + <_> + -7.80640915e-002 1.13137650e+000 7.03517556e-001 8.83319616e-001 + 5.15951753e-001 8.04341674e-001 + <_> + -5.91953993e-001 4.24855858e-001 3.52471136e-002 1.16767168e+000 + -2.76814014e-001 2.45051765e+000 + <_> + -6.39337003e-001 -5.51739573e-001 5.04474521e-001 6.70538008e-001 + 7.22837031e-001 6.84625447e-001 + <_> + -5.97123086e-001 -5.36274731e-001 5.92751622e-001 1.08256507e+000 + 4.02001053e-001 8.20149779e-001 + <_> + -2.55965620e-001 -2.08879113e-001 8.55860829e-001 6.47124171e-001 + 1.40998673e+000 -3.73535186e-001 + <_> + -1.28805304e+000 6.10506952e-001 -2.92334974e-001 8.12526047e-001 + 1.51358640e+000 -6.68419898e-001 + <_> + -9.44327563e-002 5.41537821e-001 1.04469633e+000 8.61596942e-001 + 2.34203005e+000 -9.48273718e-001 + <_> + -1.08430624e+000 6.70687929e-002 3.69881332e-001 5.45247078e-001 + -1.02620792e+000 2.20945120e+000 + <_> + -1.07009137e+000 3.79378572e-002 6.38782203e-001 4.54935372e-001 + -1.17139113e+000 1.62982166e+000 + <_> + -9.90401804e-001 -3.79162505e-002 3.57963383e-001 6.48051143e-001 + 2.65313774e-001 1.92962635e+000 + <_> + -1.04726136e+000 -1.72192663e-001 1.19198906e+000 8.54455590e-001 + -8.85765195e-001 1.42397237e+000 + <_> + -1.75369859e+000 1.42131639e+000 2.43992396e-002 -1.44182932e+000 + -8.65789950e-002 1.70029569e-002 + <_> + -1.61585712e+000 1.11466491e+000 3.95315140e-001 -1.00614786e+000 + -6.99359715e-001 3.60983819e-001 + <_> + -1.55469000e+000 1.74225175e+000 4.50696677e-001 2.29538336e-001 + 3.47496659e-001 1.43408433e-001 + <_> + -2.99902558e-001 -7.33332261e-002 2.00378847e+000 -2.94498354e-001 + 1.25861824e-001 5.06888986e-001 + <_> + -1.71579218e+000 2.55903840e-001 -2.74403244e-001 2.60849118e-001 + -4.84190941e-001 9.81883466e-001 + <_> + -1.00935507e+000 3.09781730e-001 -6.90960407e-001 5.87805927e-001 + -4.59630460e-001 -1.98114589e-001 + <_> + -1.58268905e+000 5.95580876e-001 8.96489143e-001 3.08483452e-001 + 2.15764314e-001 -1.34489989e+000 + <_> + -1.87818658e+000 4.13325459e-001 -8.32683623e-001 9.02073562e-001 + -2.13929072e-001 2.14007043e-004 + <_> + 3.27275753e-001 -2.81979918e-001 9.89903152e-001 3.23155105e-001 + -6.30164426e-003 -1.02222584e-001 + <_> + -4.62727696e-001 -2.32517076e+000 1.13506965e-001 -4.30140406e-001 + 1.65338051e+000 -1.32546103e+000 + <_> + -3.08948398e-001 -1.91016352e+000 1.58502132e-001 2.94062227e-001 + 1.32217896e+000 -1.05203724e+000 + <_> + 5.89157939e-002 -4.97958437e-003 1.08638257e-001 -6.66679591e-002 + 4.13282067e-001 -9.50104073e-002 + <_> + 8.07136118e-001 1.69659996e+000 3.10421377e-001 4.50932682e-001 + 1.40320647e+000 -2.40073577e-001 + <_> + -7.62532711e-001 1.18102968e+000 1.15793407e+000 3.07619750e-001 + -1.17127490e+000 9.60653067e-001 + <_> + -1.64902520e+000 2.02949262e+000 1.71296668e+000 -6.41567856e-002 + 1.17720163e+000 -3.87608618e-001 + <_> + -2.62857705e-001 1.25659919e+000 5.79201102e-001 4.58891898e-001 + 5.95159173e-001 2.77422404e+000 + <_> + -1.28116107e+000 2.47428823e+000 -5.43517590e-001 4.86054868e-001 + 8.39082479e-001 -7.36530602e-001 + <_> + -9.20188904e-001 1.09465194e+000 -3.71682227e-001 5.29664636e-001 + -1.98443687e+000 1.79729021e+000 + <_> + -1.02572370e+000 1.09215617e+000 3.71499419e-001 7.50854909e-001 + -1.55128491e+000 1.43628955e+000 + <_> + -1.62404144e+000 2.10947108e+000 1.31216657e+000 -1.97540179e-001 + 3.67534399e-001 -5.92973471e-001 + <_> + -1.88507867e+000 -2.78881216e+000 6.35393143e-001 1.67305911e+000 + -1.80139446e+000 1.74972892e+000 + <_> + -9.55079973e-001 9.37495649e-001 1.90329051e+000 -2.85473704e-001 + -1.65567100e+000 1.59194922e+000 + <_> + -6.88443005e-001 9.84097600e-001 3.35268259e+000 4.45227623e-001 + -5.04019320e-001 1.05877101e+000 + <_> + -3.91653240e-001 1.05592108e+000 2.86978936e+000 6.04332574e-002 + 3.98799986e-001 8.26970696e-001 + <_> + -8.64190757e-001 1.55986452e+000 1.95909894e+000 -4.60571319e-001 + 3.16464990e-001 -2.49509171e-001 + <_> + -3.18425000e-001 1.49748826e+000 2.96181321e+000 3.13001759e-002 + 9.71077621e-001 -9.33922291e-001 + <_> + -9.63695049e-001 1.03717601e+000 -6.80334628e-001 -4.95908588e-001 + 1.44692123e+000 9.18829814e-002 + <_> + -7.07826912e-001 1.55443716e+000 2.03019714e+000 -9.42143857e-001 + 1.69762230e+000 -1.00090623e+000 + <_> + -1.03175426e+000 -1.88674539e-001 1.64310312e+000 1.07811749e+000 + 6.56141818e-001 1.67940784e+000 + <_> + -6.66905284e-001 2.27352932e-001 1.82081378e+000 8.44150662e-001 + 1.84515905e+000 -6.94739878e-001 + <_> + -7.65978754e-001 7.69717023e-002 7.46035099e-001 9.76036072e-001 + -4.27015454e-001 1.10593522e+000 + <_> + -1.06707609e+000 -6.30664602e-002 6.41513646e-001 9.34738159e-001 + -3.10043376e-002 6.86905861e-001 + <_> + -9.51203167e-001 -6.86849877e-002 1.08582067e+000 1.12819099e+000 + 3.51820230e-001 2.02876186e+000 + <_> + -1.06018400e+000 1.13767125e-001 7.61056602e-001 1.24208999e+000 + 8.66889060e-002 6.51372552e-001 + <_> + -2.01259837e-001 7.08941579e-001 1.62974012e+000 2.59773821e-001 + 2.39207506e+000 -8.20870936e-001 + <_> + -9.91694093e-001 -1.73951364e+000 1.13415873e+000 8.46065104e-001 + 1.55668139e+000 -1.24661565e+000 + <_> + -1.78686666e+000 -3.70545745e-001 -1.73847663e+000 4.62619901e-001 + 3.67576212e-001 1.60777822e-001 + <_> + -1.37248099e+000 -1.69979310e+000 2.52927136e+000 1.42437458e+000 + 5.54756224e-001 1.35743245e-001 + <_> + -4.67465997e-001 3.79455864e-001 -6.84666812e-001 1.11115530e-001 + 3.16182345e-001 -2.90406346e-001 + <_> + -1.70330024e+000 -6.53392375e-001 1.69329870e+000 1.14294136e+000 + 3.37586910e-001 3.25183839e-001 + <_> + -9.43880379e-001 2.04686612e-001 2.38366580e+000 8.11842144e-001 + 1.03503120e+000 -5.42487025e-001 + <_> + -1.35395849e+000 3.18971694e-001 1.75286818e+000 9.28608775e-002 + 6.18469536e-001 -2.34515682e-001 + <_> + -1.51936817e+000 8.50771248e-001 -1.43882662e-001 -1.22024107e+000 + -4.32803273e-001 -3.24765563e-001 + <_> + -1.25919259e+000 1.68915462e+000 8.79483283e-001 -1.61315119e+000 + 1.10940015e+000 -1.12642801e+000 + <_> + -1.39272642e+000 1.89121544e+000 1.47653449e+000 -2.55297899e-001 + 2.61849856e+000 -2.13641644e+000 + <_> + -9.16742861e-001 1.75172317e+000 8.12261641e-001 5.56130894e-002 + 1.44279122e+000 -1.72196305e+000 + <_> + -7.71578550e-001 2.08862209e+000 -1.51157426e-002 2.07422447e+000 + 3.96262288e-001 -2.92596340e-001 + <_> + 1.04060495e+000 1.11257172e+000 8.72523427e-001 8.75163496e-001 + 1.48597807e-001 -1.68948114e-001 + <_> + -1.42244840e+000 1.34131122e+000 -1.94942319e+000 -9.31416571e-001 + -2.65750855e-001 3.11706573e-001 + <_> + -5.90661764e-001 -2.66983688e-001 3.01529050e-001 -8.52930307e-001 + 9.96362269e-001 -7.06827700e-001 + <_> + -1.60078073e+000 1.14480591e+000 -2.59088683e+000 6.60348058e-001 + 2.94301242e-001 5.77078700e-001 + <_> + -8.68067563e-001 -1.14728417e-002 -1.06545281e+000 + -1.56596220e+000 8.33868802e-001 -8.52005482e-001 + <_> + -6.67766750e-001 -1.46538064e-001 6.66773841e-002 -1.01189208e+000 + -4.01047230e-001 3.67793530e-001 + <_> + -7.01365590e-001 1.42070115e+000 6.14730000e-001 1.16855574e+000 + 2.52081603e-001 1.85256994e+000 + <_> + -3.05502385e-001 3.14510763e-001 -3.34209323e-001 7.37087786e-001 + -7.87303686e-001 7.71114647e-001 + <_> + -2.53381103e-001 3.14947337e-001 -3.17878395e-001 5.81888855e-001 + -4.09412295e-001 4.35021251e-001 + <_> + -4.21806037e-001 3.85070831e-001 -1.21154346e-001 -5.68017900e-001 + 2.36704612e+000 -1.74048376e+000 + <_> + -4.01991338e-001 1.02512228e+000 -5.30881464e-001 -6.60975754e-001 + -4.49069925e-002 -3.05625260e-001 + <_> + -7.54348397e-001 4.92797762e-001 -1.53141290e-001 -9.87410963e-001 + -2.71795321e+000 2.11615086e+000 + <_> + -1.72139192e+000 7.50897884e-001 -7.81198740e-001 -1.46635962e+000 + -7.50908136e-001 8.12759042e-001 + <_> + -1.49093843e+000 7.01160729e-001 6.75271571e-001 -2.56303167e+000 + -8.28124642e-001 9.23699737e-001 + <_> + -4.76081103e-001 2.83083230e-001 1.48182142e+000 -2.05396581e+000 + -3.89502853e-001 4.02124316e-001 + <_> + -1.37721920e+000 4.63516235e-001 1.45698750e+000 -7.78773651e-002 + -9.12242115e-001 1.57892919e+000 + <_> + -1.28073025e+000 3.50591451e-001 9.59612131e-001 -1.68240559e+000 + -2.85989344e-001 7.75153697e-001 + <_> + -1.65247130e+000 8.45829666e-001 1.56063795e+000 -7.81621218e-001 + 4.46993828e-001 -1.44913256e-001 + <_> + -1.58785808e+000 1.66332662e+000 -2.34516716e+000 1.39107907e+000 + -1.03591037e+000 2.45355892e+000 + <_> + -2.68888265e-001 2.22399282e+000 1.12175918e+000 -2.11503163e-001 + 2.50120193e-001 -4.56722856e-001 + <_> + -1.35395849e+000 2.20077109e+000 3.22417200e-001 -5.64970598e-002 + 2.19233942e+000 1.78933680e+000 + <_> + 1.22236684e-001 2.59369826e+000 2.07922745e+000 1.31507322e-001 + 1.54316568e+000 -4.74727362e-001 + <_> + -1.28676081e+000 1.71341062e+000 3.48479778e-001 -5.76272130e-001 + 9.04286027e-001 3.48379076e-001 + <_> + -1.69339287e+000 1.60911953e+000 7.28354335e-001 4.79922473e-001 + 1.38564169e+000 2.30136275e-001 + <_> + -1.61542642e+000 2.21196795e+000 1.86451674e+000 1.77165055e+000 + 7.02129900e-001 1.11523592e+000 + <_> + -5.00650592e-002 2.46020961e+000 2.48890543e+000 -3.35737765e-001 + 8.09022307e-001 3.55443418e-001 + <_> + -1.74163747e+000 2.20549083e+000 1.34516883e+000 1.49824154e+000 + -6.40558064e-001 1.82127619e+000 + <_> + -1.38928032e+000 2.34551191e+000 1.62149096e+000 3.75403911e-001 + 4.08141851e-001 3.54614526e-001 + <_> + -1.74120665e+000 2.07841444e+000 9.87970650e-001 1.04620016e+000 + 9.20284808e-001 1.15924573e+000 + <_> + -1.62188768e+000 1.37072945e+000 -3.45593661e-001 -3.46656322e-001 + 1.91902471e+000 -2.99891502e-001 + <_> + -1.33845139e+000 1.08536041e+000 5.12888074e-001 1.02550375e+000 + 1.12522590e+000 -1.04400241e+000 + <_> + -1.32079041e+000 2.13180852e+000 -3.90251368e-001 -4.90482032e-001 + -1.05045307e+000 -4.71901633e-002 + <_> + -8.12500238e-001 6.60427749e-001 -3.73850608e+000 -1.79972482e+000 + -1.18113601e+000 -3.29896092e-001 + <_> + -7.04380870e-001 2.49697804e+000 -1.07378554e+000 -2.44116291e-001 + -7.00314045e-001 4.68798578e-001 + <_> + -5.79031408e-001 1.93147075e+000 -2.12981343e+000 -6.65976703e-001 + -8.86396408e-001 1.15187936e-001 + <_> + -1.00049032e-002 4.45236564e-001 -1.51537561e+000 -1.53847301e+000 + -1.39307463e+000 2.77413964e-001 + <_> + -6.93612039e-001 5.19652486e-001 -1.70419669e+000 -6.45596743e-001 + -2.17987466e+000 1.70212424e+000 + <_> + -6.83704674e-001 2.15257692e+000 -7.59653032e-001 -2.25946516e-001 + -7.87660301e-001 5.37397683e-001 + <_> + -9.49049413e-001 2.16554689e+000 -1.59512377e+000 -2.71747589e-001 + -1.09526551e+000 3.73373449e-001 + <_> + -2.61996180e-001 3.88183534e-001 8.65038693e-001 6.94618344e-001 + -8.76806855e-001 9.73554432e-001 + <_> + -5.52755356e-001 1.75843239e-001 9.51360166e-001 1.43550694e-001 + 8.51123750e-001 -1.19041181e+000 + <_> + -4.87280697e-001 2.02804908e-001 1.51203156e+000 5.30671060e-001 + 5.95512569e-001 -7.15864837e-001 + <_> + -8.13361764e-001 2.59664148e-001 -9.02019441e-001 7.17548609e-001 + -7.15892553e-001 6.54563785e-001 + <_> + -2.21074522e-001 2.39149854e-001 1.22226858e+000 7.41224468e-001 + -1.58639893e-001 3.81689221e-001 + <_> + 1.28267244e-001 4.68225926e-002 5.81840277e-001 -6.39222741e-001 + 3.52497846e-001 -1.00313973e+000 + <_> + 1.67465881e-001 2.99430609e-001 7.51189351e-001 -5.37101746e-001 + 4.70793024e-002 -8.53339016e-001 + <_> + -5.90661764e-001 2.98921764e-001 6.05627120e-001 -1.74896502e+000 + -1.67935646e+000 5.67780674e-001 + <_> + -7.49179363e-001 4.97521430e-001 -1.62320530e+000 -1.24650203e-001 + -9.37779427e-001 6.56579852e-001 + <_> + -7.81055152e-001 5.53526282e-001 -1.52428806e+000 -1.13581979e+000 + 8.56003404e-001 -5.56018829e-001 + <_> + -7.90100992e-001 4.35938746e-001 -1.69235075e+000 -1.61668468e+000 + 8.52094471e-001 -6.08529747e-001 + <_> + -5.37678957e-001 1.03535044e+000 7.53234327e-001 -2.30592227e+000 + 1.78700590e+000 -1.25062525e+000 + <_> + -6.74658835e-001 6.47815824e-001 -1.10635221e+000 -4.35688436e-001 + 1.07887065e+000 -1.16689134e+000 + <_> + -6.01430595e-001 6.37522459e-001 -1.18085384e+000 -1.09145284e+000 + 9.94025767e-001 -9.83645022e-001 + <_> + -1.81745017e+000 1.54695344e+000 1.08350754e+000 -2.25277042e+000 + 1.61589265e+000 -9.90522027e-001 + <_> + -1.06966066e+000 5.17646313e-001 1.47117829e+000 -2.36978102e+000 + 8.51935446e-001 -9.61717129e-001 + <_> + -1.75283706e+000 1.19474733e+000 2.51676738e-001 -1.67510784e+000 + 1.61582935e+000 -1.00380015e+000 + <_> + -1.53013706e+000 2.33148098e+000 -9.34095800e-001 1.01581788e+000 + 8.78550410e-001 3.99703234e-001 + <_> + -1.65591729e+000 2.58003688e+000 -1.61405349e+000 6.81124985e-001 + 1.05574977e+000 4.01594549e-001 + <_> + -9.04250979e-001 2.49326253e+000 4.17708874e-001 1.97531319e+000 + -4.03105944e-001 2.46036842e-001 + <_> + -1.26694608e+000 2.61171722e+000 -5.84817946e-001 1.23213434e+000 + -7.24602789e-002 6.40396237e-001 + <_> + -5.72570086e-001 2.02068424e+000 -1.59815514e+000 9.74913359e-001 + 1.02645683e+000 -6.24437809e-001 + <_> + -1.38755739e+000 2.03432345e+000 -6.16920292e-001 4.13192064e-001 + 2.33464390e-001 2.55209655e-001 + <_> + -1.33802056e+000 1.84639597e+000 -4.34586018e-001 1.73142910e+000 + 7.59113804e-002 -1.24281114e-002 + <_> + -1.35955834e+000 1.91250491e+000 -1.25930119e+000 1.38504040e+000 + 5.40183038e-002 -7.75619820e-002 + <_> + -5.64385712e-001 2.53176904e+000 -3.82420480e-001 1.87195575e+000 + 7.61425436e-001 -5.05901992e-001 + <_> + -1.88163257e+000 -1.87339473e+000 -5.68262041e-001 1.08691835e+000 + -3.15843314e-001 6.59465253e-001 + <_> + -1.82306647e-001 1.06450546e+000 1.82334626e+000 2.05550417e-001 + 2.88775253e+000 -2.38281441e+000 + <_> + -1.01840091e+000 9.61121798e-001 2.37402654e+000 2.62338549e-001 + 2.17028999e+000 -1.52528346e+000 + <_> + -9.04681742e-001 9.34960961e-001 1.31848025e+000 7.94720531e-001 + 2.02788782e+000 -1.64402258e+000 + <_> + -8.84867013e-001 9.82354820e-001 1.95584548e+000 6.15753233e-001 + 2.23404813e+000 -1.86355019e+000 + <_> + -1.56545889e+000 9.68341649e-001 1.38560042e-001 6.61848709e-002 + 2.53156209e+000 -2.05087876e+000 + <_> + -1.25445426e+000 1.09562540e+000 2.26586199e+000 9.39480603e-001 + 1.04627013e+000 -8.36573780e-001 + <_> + -1.63179505e+000 7.33733952e-001 -5.62862635e-001 1.08610046e+000 + -1.92254171e-001 1.12140989e+000 + <_> + -1.29322219e+000 1.39204597e+000 1.98932064e+000 7.53972471e-001 + 1.61932337e+000 -9.92917657e-001 + <_> + -1.67400897e+000 7.04033732e-001 3.01505439e-002 1.04300809e+000 + 2.35082313e-001 9.82848331e-002 + <_> + -1.43838632e+000 1.56257129e+000 1.89336848e+000 7.86792576e-001 + 1.01635432e+000 -7.49260902e-001 + <_> + -9.18482319e-002 -1.62879854e-001 1.41949153e+000 -1.77135870e-001 + 1.47775248e-001 -7.79686987e-001 + <_> + -1.98244557e-001 -1.18090533e-001 7.72192895e-001 -3.72808427e-002 + -3.64002973e-001 3.71867210e-001 + <_> + -1.49569318e-001 1.05031338e-002 7.44517982e-001 9.33478773e-002 + 3.30636322e-001 -5.91571987e-001 + <_> + 3.56550589e-002 2.99661234e-002 1.68664169e+000 1.08822078e-001 + 2.51875699e-001 -5.36082566e-001 + <_> + -1.02186330e-001 9.01998878e-002 1.35907686e+000 -1.27054501e+000 + 2.18560576e-001 -6.84725761e-001 + <_> + -5.66108763e-001 3.23693573e-001 8.36648464e-001 -1.27784216e+000 + 2.77742684e-001 -1.70178223e+000 + <_> + -6.82412446e-001 2.78579563e-001 -1.90668356e+000 -1.57078171e+000 + -4.69945222e-001 1.21131830e-001 + <_> + -2.15905488e-001 1.18416166e+000 -2.13550180e-001 -8.27534378e-001 + 7.64454782e-001 -1.83642733e+000 + <_> + -7.41425753e-001 2.98066944e-001 -2.75634217e+000 -1.38614583e+000 + -2.58633912e-001 6.04998320e-002 + <_> + -4.75219578e-001 6.90013096e-002 -6.59636855e-001 -2.67440051e-001 + 3.70718628e-001 -3.93634886e-001 + <_> + -1.08430624e+000 1.38352096e-001 -1.42569101e+000 5.57108447e-002 + 8.96555007e-001 -1.07910752e+000 + <_> + -6.39337003e-001 2.43212923e-001 -7.84452260e-001 -2.65303278e+000 + -7.56811976e-001 3.13196063e-001 + <_> + -7.88808763e-001 3.86255711e-001 -2.74651814e+000 -6.44098520e-001 + -4.57553416e-001 1.65952832e-001 + <_> + -1.18553352e+000 8.71959478e-002 -1.44396019e+000 1.04138565e+000 + -9.38125923e-002 -2.52999246e-001 + <_> + -1.40263379e+000 2.39050731e-001 -2.58669877e+000 -5.54388940e-001 + 7.17219889e-001 -6.42757833e-001 + <_> + -1.67230234e-001 4.34162408e-001 -3.06869000e-001 -2.84841508e-001 + -1.34502876e+000 1.37132359e+000 + <_> + -3.29624623e-001 5.69898307e-001 -8.58622193e-001 -3.54322881e-001 + -8.09263885e-001 8.20309222e-001 + <_> + -1.54822874e+000 7.48973012e-001 -2.52597898e-001 4.39197719e-001 + 1.18917465e-001 -4.63529170e-001 + <_> + -6.13060951e-001 7.09464967e-001 -1.23394525e+000 4.82806742e-001 + 7.50167251e-001 -4.45387036e-001 + <_> + -9.72740889e-001 1.77284825e+000 -4.81998235e-001 4.73635852e-001 + 6.74977958e-001 -1.04474378e+000 + <_> + -1.65505576e+000 1.26391006e+000 -3.52965951e-001 5.02740562e-001 + 1.03878307e+000 -1.32162368e+000 + <_> + -8.48252833e-001 8.50022316e-001 -2.25549984e+000 2.65897453e-001 + 3.48687440e-001 -2.55070150e-001 + <_> + -1.51979887e+000 1.23776495e+000 2.59904552e+000 -1.13787365e+000 + 1.30003595e+000 -9.57896233e-001 + <_> + -1.10498250e+000 -1.17527401e+000 1.99574113e+000 1.09420128e-001 + 1.30063117e+000 -1.32947719e+000 + <_> + -5.17880730e-002 1.62510544e-001 2.01769924e+000 1.23878539e-001 + 1.05886424e+000 -1.05462015e+000 + <_> + -1.67314744e+000 3.63812923e-001 2.59713030e+000 5.39622605e-001 + 1.23210359e+000 -1.73658764e+000 + <_> + -1.74034512e+000 -7.62447417e-001 2.66943407e+000 4.23324019e-001 + 1.15499973e+000 -1.37696648e+000 + <_> + -1.65419424e+000 4.12585437e-001 1.68883669e+000 1.74306273e-001 + 1.33402300e+000 -1.84965193e+000 + <_> + -7.33672202e-001 1.69009304e+000 3.00593448e+000 3.86110619e-002 + 2.07980204e+000 -1.51403570e+000 + <_> + -1.53185999e+000 1.43769896e+000 3.32356513e-001 2.52499849e-001 + 2.54282093e+000 -1.89032876e+000 + <_> + -1.26048481e+000 2.22846365e+000 2.26156449e+000 -6.22539222e-001 + 2.02825093e+000 -1.41907966e+000 + <_> + -1.70330024e+000 1.25597572e+000 9.42609191e-001 -3.91690433e-001 + 2.09994507e+000 -1.71183813e+000 + <_> + -9.03389454e-001 1.72249877e+000 1.04009008e+000 1.38124609e+000 + 1.28343713e+000 -1.54801261e+000 + <_> + -1.18079531e+000 1.03787196e+000 6.69947147e-001 2.07127810e+000 + 1.02054811e+000 -2.04381675e-001 + <_> + -4.08021897e-001 1.54840505e+000 7.33569145e-001 2.22751522e+000 + 1.47186792e+000 -8.52263272e-001 + <_> + -6.35460198e-001 1.19075739e+000 1.26910388e+000 6.71005487e-001 + 2.16313696e+000 1.62724912e+000 + <_> + -5.22602558e-001 1.73172569e+000 8.62601459e-001 2.03778386e+000 + 1.91912699e+000 -8.62722576e-001 + <_> + -7.06965446e-001 1.56399250e+000 5.40828347e-001 2.34889507e+000 + 5.24380684e-001 5.58528006e-001 + <_> + -1.94814987e-002 1.26619589e+000 1.09117115e+000 1.08624947e+000 + 1.73575068e+000 -1.13606727e+000 + <_> + -6.75089598e-001 1.19098210e+000 9.91166472e-001 2.31790018e+000 + 8.08117092e-001 6.19895995e-001 + <_> + -1.31691360e+000 4.12721038e-001 -7.31986761e-001 -2.71844447e-001 + -5.48160791e-001 -4.13306236e-001 + <_> + -1.22001030e-001 1.74434349e-001 1.32116556e+000 -1.33301950e+000 + -1.25658190e+000 1.24584663e+000 + <_> + -3.25747818e-001 1.39403611e-001 1.06958210e+000 -1.05555880e+000 + -2.27260423e+000 1.91035986e+000 + <_> + -1.59907416e-001 9.29545537e-002 8.50519061e-001 -1.33964264e+000 + -1.14732742e+000 9.78087723e-001 + <_> + -1.94367766e-001 4.90505636e-001 1.81644404e+000 -4.76315200e-001 + -1.91511881e+000 1.86279893e+000 + <_> + 3.11768591e-001 4.14574802e-001 -1.26153362e+000 -7.94516385e-001 + -9.54842925e-001 1.31425524e+000 + <_> + -2.98179537e-001 8.82843733e-001 1.38505292e+000 -3.61194789e-001 + 1.07651734e+000 -3.39407846e-002 + <_> + -1.30399096e+000 1.49791050e+000 -1.36900282e+000 -1.45965874e+000 + -2.68950075e-001 6.89592719e-001 + <_> + -1.56461388e-001 1.12004906e-001 1.10085952e+000 -1.28523982e+000 + 3.11599493e-001 -6.98219836e-002 + <_> + -2.38304690e-001 6.99722618e-002 3.01981896e-001 -1.83062017e+000 + 3.17695141e-001 2.29038903e-003 + <_> + -5.66539526e-001 8.62873435e-001 1.22883606e+000 6.13321364e-001 + 5.37259996e-001 1.52251184e+000 + <_> + -8.28188565e-003 1.50345504e-001 1.86854854e-001 -7.85719812e-001 + -9.48607266e-001 6.58924401e-001 + <_> + 4.42701429e-002 2.01939568e-001 8.41678157e-002 -6.48555934e-001 + -1.05672169e+000 7.23564386e-001 + <_> + -3.12394440e-001 4.46758687e-001 -9.69867483e-002 -1.51273298e+000 + -2.51292443e+000 1.52517629e+000 + <_> + -2.11167186e-001 2.35297352e-001 -7.04510868e-001 -1.06075895e+000 + -1.27310300e+000 7.71767795e-001 + <_> + -6.34184405e-002 3.99319410e-001 -3.14019173e-001 -2.48083210e+000 + -1.55680156e+000 1.03275049e+000 + <_> + -1.89629465e-001 5.01598835e-001 -8.63407776e-002 -2.15273738e+000 + -2.29373908e+000 1.52419770e+000 + <_> + 4.38393913e-002 2.15520203e-001 -3.97949576e-001 2.08652794e-001 + -2.54936409e+000 1.58640933e+000 + <_> + 8.94828793e-003 -1.66426990e-002 3.40795994e-001 -1.34326851e+000 + -1.48805642e+000 9.01622236e-001 + <_> + -7.97871053e-002 1.38789341e-001 -7.05817461e-001 -6.09561086e-001 + -2.14100552e+000 1.29100454e+000 + <_> + -8.62484202e-002 -1.38470875e-002 5.76841772e-001 3.58055681e-001 + 1.04227996e+000 -9.61104482e-002 + <_> + -4.73496556e-001 1.03616945e-001 8.24845850e-001 6.60968184e-001 + 1.99331844e+000 -9.35761511e-001 + <_> + -8.02592874e-001 2.31949717e-001 -1.52547121e-001 8.80019486e-001 + -4.58019167e-001 1.11454701e+000 + <_> + -1.64041018e+000 3.90314519e-001 3.63178194e-001 5.74147701e-002 + 8.30731869e-001 -9.14712310e-001 + <_> + 5.11622131e-002 5.27537279e-002 -1.38239181e+000 -5.39307296e-001 + 1.54956087e-001 -8.73531178e-002 + <_> + 1.27622759e+000 -7.11695552e-002 1.31900176e-001 -5.80491185e-001 + 1.04783855e-001 -1.86705645e-002 + <_> + 5.01731277e-001 2.40255296e-001 -1.78738284e+000 -8.21115911e-001 + 4.09757420e-002 -2.49541458e-002 + <_> + 3.37183118e-001 4.34051424e-001 -1.30681145e+000 -8.37708414e-001 + 3.22892129e-001 -2.81684399e-001 + <_> + 1.44635901e-001 1.68896839e-001 -8.38734865e-001 -7.36771941e-001 + 9.92691219e-001 -7.22422659e-001 + <_> + 4.77609038e-001 2.65171677e-001 -1.19327950e+000 9.93010461e-001 + 8.19009319e-002 -1.37174800e-001 + <_> + 1.53941846e+000 -2.69349873e-001 1.00176334e+000 -2.46544749e-001 + -2.02726468e-001 9.78622288e-002 + <_> + 1.49892747e+000 3.41086417e-001 6.28222764e-001 -5.80512524e-001 + -2.19103619e-001 3.95149775e-002 + <_> + 1.39468503e+000 2.70434856e-001 1.01572049e+000 3.31342757e-001 + -2.17032269e-001 7.61992484e-002 + <_> + 6.12435162e-001 1.78273201e-001 -8.39236736e-001 -1.39145327e+000 + -1.95572600e-001 3.14440504e-002 + <_> + 1.56784821e+000 -2.78484792e-001 2.54087467e-002 -1.40317833e+000 + -1.06496322e+000 8.23228240e-001 + <_> + 1.49978900e+000 4.54130679e-001 1.42954624e+000 -1.62812150e+000 + -3.32988203e-001 -9.87872481e-003 + <_> + -1.37077436e-001 3.92650627e-002 2.36378372e-001 2.89450675e-001 + 2.28334382e-001 -5.50251067e-001 + <_> + -2.26674333e-001 1.42095834e-001 -1.42706409e-002 2.11507455e-001 + 1.08719096e-001 -3.48963171e-001 + <_> + 2.53169537e-002 -4.99078669e-002 5.63775837e-001 4.26938161e-002 + 3.16133320e-001 2.26793177e-002 + <_> + 1.15775369e-001 4.41675112e-002 1.88383535e-001 -2.57232219e-001 + -1.34072334e-001 -1.36735393e-002 + <_> + -8.58176649e-002 -3.34513700e-003 -9.47398767e-002 2.95047879e-001 + 5.43053448e-001 -6.80029299e-003 + <_> + -1.69814765e-001 -3.52855586e-002 -1.10107452e-001 1.29211783e-001 + 4.66411233e-001 1.90053359e-001 + <_> + -1.35354415e-001 -6.22883998e-002 9.33774039e-002 1.82690889e-001 + 8.95037293e-001 -8.57077479e-001 + <_> + 1.46358922e-001 6.66665673e-001 -1.41017926e+000 -9.88339856e-002 + -1.15299594e+000 2.78902674e+000 + <_> + 3.91010903e-002 4.53870654e-001 -1.49848509e+000 -3.79655659e-001 + -1.76942110e+000 1.57169831e+000 + <_> + 2.06233785e-001 1.77452481e+000 7.12306023e-001 1.35950446e+000 + -1.40498936e-001 3.74058843e-001 + <_> + -7.22041845e-001 1.96970987e+000 -1.72873914e+000 1.08441794e+000 + -3.95587415e-001 2.24205184e+000 + <_> + -1.37508184e-001 1.81880569e+000 2.76972085e-001 4.13033813e-001 + 8.87485385e-001 -7.07869828e-001 + <_> + -6.81981683e-001 2.09441662e+000 -7.51842856e-002 1.77210128e+000 + -3.62987220e-001 9.61540341e-001 + <_> + -1.26479232e+000 4.09700096e-001 7.91237950e-001 1.14953828e+000 + -2.67023683e-001 6.91492379e-001 + <_> + -1.71665370e+000 -1.73681879e+000 -2.78527796e-001 1.97752881e+000 + -1.91373274e-001 3.94897699e-001 + <_> + -1.77437472e+000 -1.43952930e+000 6.95629001e-001 1.37635243e+000 + -1.96569324e-001 3.88242692e-001 + <_> + -1.41469491e+000 4.95868623e-001 1.18493986e+000 4.46732044e-001 + 2.39789084e-001 2.30270438e-002 + <_> + -9.92986381e-001 1.19871545e+000 2.18123651e+000 -1.38430119e+000 + -1.67926741e+000 9.12777543e-001 + <_> + -1.06406081e+000 1.19800723e+000 1.04623938e+000 -1.68613285e-001 + -1.72171259e+000 2.25428104e+000 + <_> + -7.69424796e-001 6.84855640e-001 7.99588025e-001 -2.89534181e-001 + -2.17874274e-001 2.82184434e+000 + <_> + -7.96131551e-001 1.03964734e+000 1.37190056e+000 -7.79015385e-003 + 2.54401505e-001 2.26193881e+000 + <_> + -9.35696006e-001 7.34093368e-001 1.44741452e+000 -1.66060716e-001 + -1.77172327e+000 9.70870197e-001 + <_> + -1.29623747e+000 1.50223091e-001 1.80633461e+000 4.03647795e-002 + -4.02374774e-001 -2.66846508e-001 + <_> + -1.36903489e+000 1.05521691e+000 2.60568523e+000 2.13225067e-001 + -5.82436621e-001 1.65033293e+000 + <_> + -1.81055808e+000 -1.26719820e+000 1.98884189e+000 8.23014200e-001 + -7.26485485e-003 2.08711791e+000 + <_> + -9.92986381e-001 2.98791081e-001 -8.93914521e-001 -2.58026719e-001 + -1.70635736e+000 7.16851175e-001 + <_> + -4.08021897e-001 1.10182858e+000 1.31942630e+000 -5.35899401e-001 + -1.10150850e+000 3.17105830e-001 + <_> + 6.92538992e-002 3.93279970e-001 -5.45476019e-001 -1.14470077e+000 + 1.74147534e+000 -2.31366396e+000 + <_> + -1.84458768e+000 1.64023364e+000 -3.91569495e-001 -3.44615608e-001 + 2.21364784e+000 -1.41214108e+000 + <_> + 8.89410138e-001 2.15748459e-001 1.03137946e+000 -1.50517654e+000 + -3.49111706e-001 1.55256465e-001 + <_> + 1.54096028e-002 3.43280077e-001 -1.01115870e+000 -6.76244259e-001 + 4.54533875e-001 2.31537133e-001 + <_> + -8.92636999e-002 2.53565401e-001 4.44768891e-002 -3.32863927e+000 + 9.07275319e-001 -8.79081070e-001 + <_> + 2.54478276e-001 1.33294791e-001 -2.44008437e-001 -2.99156237e+000 + 5.04536152e-001 -6.53715432e-001 + <_> + -1.55253625e+000 1.04754186e+000 -9.62835014e-001 -5.79929166e-002 + 2.04330850e+000 -1.15906036e+000 + <_> + -4.17929262e-001 2.71075279e-001 1.00662637e+000 -2.13955498e+000 + 8.58711421e-001 -1.08301401e+000 + <_> + -1.42417145e+000 1.72521675e+000 1.25509775e+000 -3.57367754e+000 + 2.79275012e+000 -3.31258178e+000 + <_> + -4.23098296e-001 4.14072633e-001 1.01401019e+000 -3.19782257e+000 + 1.36817729e+000 -1.33097160e+000 + <_> + 5.80542833e-002 -2.02313680e-002 3.20490479e-001 1.80759281e-001 + -1.97324559e-001 3.49118918e-001 + <_> + 3.36321592e-001 -1.06770694e-001 -6.22051477e-001 -2.68509650e+000 + -1.71369985e-001 -3.32581818e-001 + <_> + -8.28438163e-001 9.45101157e-002 -2.01343465e+000 2.30912432e-001 + -6.24085128e-001 -6.68556467e-002 + <_> + 4.65117157e-001 -7.92464614e-002 1.30783413e-002 -2.11887288e+000 + -1.54546380e-001 -3.61536711e-001 + <_> + -6.10907197e-001 8.87629483e-003 1.02255058e+000 4.26584363e-001 + 2.34278500e-001 -1.96082041e-001 + <_> + -1.60336530e+000 8.63840953e-002 -5.54152012e-001 -7.52009928e-001 + 1.18656933e+000 -1.02924418e+000 + <_> + -2.63735689e-002 3.15714657e-001 1.00420877e-001 1.10788071e+000 + -1.33396640e-001 5.33143282e-001 + <_> + -8.25853646e-001 1.41979098e+000 -8.30958784e-001 1.66988635e+000 + -1.35356322e-001 4.84088451e-001 + <_> + -1.52022970e+000 1.15657091e+000 4.37535584e-001 -4.61717701e+000 + -7.15077817e-002 -4.96024430e-001 + <_> + -1.55770528e+000 5.49732208e-001 6.92733169e-001 -2.56728148e+000 + 7.77077138e-001 -8.75601053e-001 + <_> + -3.95099282e-001 5.19916952e-001 5.36201894e-001 -2.22523069e+000 + 2.76076645e-001 -4.83841896e-001 + <_> + -1.86483312e+000 2.03764725e+000 8.29475164e-001 -9.95008588e-001 + 7.54831553e-001 -9.73851681e-001 + <_> + -1.84889519e+000 1.90455067e+000 -1.63738221e-001 -2.54666615e+000 + 6.61292255e-001 -7.43251145e-001 + <_> + -1.54176736e+000 -5.27530313e-001 3.42918843e-001 1.26105785e+000 + 1.48325586e+000 -3.03113580e-001 + <_> + -1.71449983e+000 1.63574350e+000 -8.79824698e-001 -3.21399951e+000 + 7.55165398e-001 -8.65427852e-001 + <_> + -1.45690882e+000 4.87367898e-001 -4.72110838e-001 -3.08357096e+000 + 2.04140812e-001 -3.55379105e-001 + <_> + -1.86612546e+000 9.91983831e-001 -3.03205937e-001 -1.32574427e+000 + 7.42395520e-001 -4.19014722e-001 + <_> + -1.40263379e+000 2.43595362e-001 1.39370012e+000 -5.86260796e-001 + -1.44036591e+000 1.93986845e+000 + <_> + -1.77523625e+000 1.16381562e+000 1.64338863e+000 -1.44116795e+000 + -1.03725469e+000 1.12599170e+000 + <_> + -1.51247609e+000 4.34251696e-001 3.97934079e-001 -1.53966618e+000 + 4.25043285e-001 -5.80307007e-001 + <_> + -1.47887731e+000 3.08304846e-001 1.25966644e+000 2.05026999e-001 + 7.07027078e-001 8.58713984e-002 + <_> + -1.75757527e+000 7.07071245e-001 1.24630058e+000 -1.09160316e+000 + 1.13814044e+000 -1.20388138e+000 + <_> + -1.52884471e+000 7.74570629e-002 3.26449454e-001 -1.15256631e+000 + 1.98209256e-001 3.30930613e-002 + <_> + -1.68047035e+000 3.06659192e-001 1.27878356e+000 -1.96442194e-002 + -7.32554123e-002 4.28586245e-001 + <_> + -1.80668128e+000 1.02317321e+000 1.74666989e+000 -1.24241400e+000 + 7.77168572e-001 -8.79644036e-001 + <_> + -1.59948850e+000 3.59204292e-001 8.57178926e-001 -1.59070027e+000 + 9.76000801e-002 -5.82173541e-002 + <_> + -3.54608357e-001 1.08460379e+000 -1.65690851e+000 -1.27961946e+000 + -4.69045401e-001 -1.01619452e-001 + <_> + -1.18208754e+000 1.13836062e+000 -1.60872328e+000 8.99095610e-002 + -5.78467429e-001 1.95675266e+000 + <_> + 2.90230900e-001 2.82662302e-001 5.93281761e-002 -2.84575313e-001 + -4.56683218e-001 6.24954641e-001 + <_> + -2.85256922e-001 2.03610826e+000 -1.34960878e+000 -5.70213854e-001 + 1.69870704e-001 4.44257826e-001 + <_> + -1.67314744e+000 -8.94582331e-001 -4.28358823e-001 4.11419660e-001 + -4.34186935e-001 1.27229786e+000 + <_> + -1.23293296e-001 6.08440161e-001 1.92911893e-001 2.46537641e-001 + 7.41858304e-001 -1.52460063e+000 + <_> + -1.71536136e+000 -2.10871029e+000 2.92749226e-001 1.23540986e+000 + 8.49860162e-002 -2.46173039e-001 + <_> + -1.65548658e+000 -1.46339297e+000 -7.99116075e-001 7.06559181e-001 + 1.57733047e+000 -1.70494425e+000 + <_> + -1.83554184e+000 -1.63681042e+000 1.45032421e-001 5.37681401e-001 + 1.82340884e+000 -1.93602884e+000 + <_> + -1.77954376e+000 -1.02583015e+000 -7.36786246e-001 3.59515160e-001 + 1.86520422e+000 -1.64955175e+000 + <_> + -1.68650091e+000 -1.94438422e+000 7.19159186e-001 7.48750925e-001 + 2.50568569e-001 -8.26277025e-003 + <_> + -1.65031755e+000 -4.10728902e-001 -4.61191386e-002 1.29349017e+000 + 5.88136673e-001 5.81705451e-001 + <_> + -7.52194643e-001 -3.48648757e-001 1.12825167e+000 1.34119833e+000 + 3.01363647e-001 1.09191239e-001 + <_> + -1.03132355e+000 -1.91782981e-001 7.03315675e-001 1.50296438e+000 + 4.82873261e-001 -1.88372821e-001 + <_> + -1.48189259e+000 7.16637745e-002 2.24263743e-002 1.63970411e+000 + 2.43956059e-001 2.25828782e-001 + <_> + -7.59517431e-001 -2.71753222e-001 1.16025889e+000 1.30877113e+000 + 2.26309133e+000 2.07328185e-001 + <_> + -5.83769679e-001 -4.22392458e-001 1.11459160e+000 1.73557794e+000 + 1.66350603e+000 -2.46434167e-001 + <_> + -1.48404634e+000 -1.56129336e+000 5.99244177e-001 2.01890969e+000 + 3.78293335e-001 1.37700945e-001 + <_> + -1.08689082e+000 -6.05786204e-001 7.92181134e-001 1.65522063e+000 + 2.99940318e-001 2.73439765e-001 + <_> + -4.75650340e-001 -9.79477346e-001 1.33581189e-002 1.86499333e+000 + 1.27906120e+000 -4.47216511e-001 + <_> + -1.38195753e+000 1.41411081e-001 9.53113794e-001 1.13313055e+000 + 9.40024078e-001 -8.62875134e-002 + <_> + -1.41771019e+000 4.76281881e-001 5.95808983e-001 6.07583284e-001 + 2.39259791e+000 -1.23318958e+000 + <_> + -7.62102008e-001 9.68253985e-002 -1.63259432e-001 8.88158798e-001 + -9.01735008e-001 2.23002863e+000 + <_> + -1.82057067e-003 3.35511088e-001 1.06246364e+000 2.12718368e-001 + 3.80631387e-001 1.52337563e+000 + <_> + -6.93181276e-001 4.31939900e-001 7.31783748e-001 -2.42109671e-001 + 4.05585110e-001 1.27680659e+000 + <_> + -1.04770862e-001 4.38297540e-001 4.49470319e-002 1.57039493e-001 + 8.27785023e-003 1.88447285e+000 + <_> + 1.75633747e-002 2.75999665e-001 1.52559078e+000 4.29099113e-001 + -5.67846708e-002 2.11084199e+000 + <_> + 7.09769130e-002 2.61534005e-001 1.36767209e+000 5.55987775e-001 + -9.19705212e-001 2.44258475e+000 + <_> + -1.65290201e+000 8.82117093e-001 -2.04354954e+000 -2.44067959e-003 + -3.15325111e-001 5.71445525e-001 + <_> + -1.16011906e+000 6.94890320e-001 -1.48843676e-001 -1.21497619e+000 + -1.46076870e+000 1.73107171e+000 + <_> + -1.72613025e+000 1.31259036e+000 -5.32136142e-001 -1.37109786e-001 + -3.41828525e-001 4.13817763e-001 + <_> + -4.10175681e-001 1.51646888e+000 -2.27960169e-001 -3.91020566e-001 + 6.66248798e-001 3.68681282e-001 + <_> + -1.12738168e+000 7.91530132e-001 -1.22471833e+000 -4.31708634e-001 + -1.35837102e+000 1.73627377e+000 + <_> + -1.65117896e+000 1.58000934e+000 -4.00955021e-001 1.62165493e-001 + -7.29347160e-003 4.97206539e-001 + <_> + -5.28202355e-001 5.13697624e-001 -1.00399983e+000 -5.94604500e-002 + 1.07985055e+000 5.83272815e-001 + <_> + -1.75671387e+000 1.54476058e+000 8.56048286e-001 -1.99574381e-001 + 1.72118080e+000 -8.54911804e-001 + <_> + -9.82217491e-001 1.09486449e+000 1.65021873e+000 -4.37230736e-001 + -2.14731514e-001 -1.04328620e+000 + <_> + -1.51764512e+000 7.96002507e-001 -4.78617847e-001 5.95836878e-001 + 6.68460548e-001 -3.74825239e-001 + <_> + -1.25016317e-001 1.88280165e-001 1.42297578e+000 5.80184571e-002 + 1.56405509e+000 -9.74980295e-001 + <_> + -1.10584402e+000 9.73671257e-001 1.65143871e+000 1.10615027e+000 + -9.18035388e-001 1.38449347e+000 + <_> + -6.93181276e-001 2.05229187e+000 2.41910473e-001 1.71488869e+000 + 6.45766854e-001 1.59663916e+000 + <_> + -7.84070432e-001 2.00674891e+000 -1.77693032e-002 1.62236118e+000 + 9.49527204e-001 1.83522546e+000 + <_> + -9.06835496e-001 1.33710444e+000 1.06125224e+000 4.70348150e-001 + -2.68929005e-001 1.56224161e-001 + <_> + -1.18854880e+000 2.47103548e+000 2.15495452e-002 6.68255329e-001 + 1.39090812e+000 -9.85099196e-001 + <_> + -1.03132355e+000 2.54933739e+000 2.11040378e+000 1.31628919e+000 + 5.03667414e-001 9.02326047e-001 + <_> + -1.13513529e+000 2.15738368e+000 8.80977333e-001 9.72992480e-001 + 1.03300548e+000 -4.38557446e-001 + <_> + -1.44829369e+000 2.12879181e+000 1.51369244e-001 1.37476420e+000 + 2.29572296e-001 8.56437683e-001 + <_> + 5.79697788e-001 1.68628228e+000 9.53307092e-001 1.47070348e-001 + 7.10332870e-001 2.61490703e-001 + <_> + -5.55770636e-001 -1.89804822e-001 1.24438250e+000 9.30601418e-001 + -3.64602476e-001 5.24023712e-001 + <_> + -9.74480361e-002 -1.92536795e+000 2.74470592e+000 7.61360765e-001 + 7.19389081e-001 -2.55711913e-001 + <_> + -6.30291164e-001 1.31470263e+000 1.79352248e+000 7.10179865e-001 + -7.82961398e-003 2.48720813e+000 + <_> + -1.43278658e+000 1.07603455e+000 3.25843751e-001 2.71261752e-001 + -1.11109746e+000 8.09230626e-001 + <_> + 2.01495484e-001 1.99425709e+000 1.54842579e+000 6.39556527e-001 + 1.92631841e-001 1.18511960e-001 + <_> + -1.62490296e+000 -4.67094719e-001 9.81082916e-001 4.62113500e-001 + -1.32874653e-001 -2.47924924e-002 + <_> + -1.44269395e+000 -3.78878206e-001 1.44703090e+000 3.35001230e-001 + -1.64075390e-001 -1.82641111e-003 + <_> + -3.11283371e-003 7.70375669e-001 1.84037232e+000 -1.19546808e-001 + 1.21263015e+000 -2.54727751e-001 + <_> + -1.53530610e+000 -6.25571534e-002 1.02714813e+000 -5.46182454e-001 + -1.75140917e-001 -7.49496967e-002 + <_> + -6.25983596e-001 -9.57271338e-001 1.24758697e+000 1.02285100e-002 + -2.93358356e-001 -1.06246799e-001 + <_> + -1.79849696e+000 -1.29542601e+000 1.48427594e+000 -1.22440308e-002 + -5.37871271e-002 -2.72549242e-001 + <_> + -6.72074318e-001 6.11886621e-001 1.08661331e-001 -7.23817825e-001 + -6.98714614e-001 -9.97019261e-002 + <_> + -1.66496313e+000 -3.52654994e-001 2.80179441e-001 -2.65843511e-001 + -4.91507053e-001 -1.32992283e-001 + <_> + -6.12646714e-002 5.54391623e-001 2.75541283e-003 -1.37310338e+000 + -9.90171015e-001 1.76806897e-001 + <_> + -7.05242395e-001 6.41312063e-001 -6.95679128e-001 -5.20647526e-001 + -8.37552488e-001 -3.98796499e-002 + <_> + 1.86419085e-001 4.63461846e-001 1.33713305e+000 -1.09781194e+000 + -6.84441447e-001 2.04094145e-002 + <_> + -9.83940542e-001 1.21153212e+000 3.62439811e-001 -4.15752023e-001 + -4.57308233e-001 1.89804471e+000 + <_> + -1.33715904e+000 1.21720767e+000 -9.31499898e-001 -7.41484106e-001 + 3.44533205e-001 1.50114608e+000 + <_> + -1.15408850e+000 1.69330478e+000 2.15305641e-001 9.43313166e-003 + -5.67573607e-001 2.87257373e-001 + <_> + -6.50105834e-001 9.83202279e-001 -6.56106472e-001 4.73108083e-001 + -2.37830296e-001 1.83469892e+000 + <_> + -2.54673362e-001 1.66839004e+000 6.43930733e-001 2.28938088e-001 + -5.87355077e-001 2.38715932e-001 + <_> + -1.08430624e+000 1.20672476e+000 -2.32181001e+000 -1.08082771e-001 + 2.54629314e-001 1.46094716e+000 + <_> + -1.73431456e+000 1.73075604e+000 4.12188321e-001 1.93496491e-003 + 5.84460199e-001 -6.42628074e-001 + <_> + -1.88594007e+000 1.68190277e+000 2.83346415e-001 -1.89335451e-001 + 8.76144409e-001 -6.54311717e-001 + <_> + -1.75972915e+000 1.77214518e-001 6.54112279e-001 1.59137249e+000 + 1.79069090e+000 8.36293399e-001 + <_> + -8.59021723e-001 7.50578821e-001 -2.11115822e-001 1.68039393e+000 + 1.57998133e+000 8.22127342e-001 + <_> + -4.03283596e-001 9.53976691e-001 -1.19763482e+000 1.77326846e+000 + 7.32760489e-001 5.90481699e-001 + <_> + -1.11058235e+000 4.22482759e-001 5.05530179e-001 1.48682106e+000 + 3.09698307e-003 2.37921786e+000 + <_> + -8.62898469e-001 4.62650448e-001 1.15018976e+000 7.85300255e-001 + 1.20474899e+000 1.68895197e+000 + <_> + -1.05846107e+000 3.09885561e-001 1.04753733e+000 9.14560378e-001 + 1.34429157e+000 2.05697036e+000 + <_> + -1.40004921e+000 1.36188865e-001 1.05971491e+000 1.85943639e+000 + 2.88045734e-001 2.66547537e+000 + <_> + -1.24454689e+000 6.75861612e-002 1.77508605e+000 1.42316115e+000 + 1.74230897e+000 2.18894482e+000 + <_> + -1.68262410e+000 3.35287466e-003 -2.39018589e-001 1.76428282e+000 + 5.59265673e-001 2.94875026e+000 + <_> + -1.33026707e+000 1.45013630e-001 1.10519457e+000 1.31555092e+000 + 1.82519519e+000 2.17290401e+000 + <_> + -1.07956803e+000 6.40582561e-001 1.62029684e+000 1.25370634e+000 + 1.11455488e+000 -9.86239240e-002 + <_> + -1.40263379e+000 -3.25289190e-001 2.02918172e+000 1.74097133e+000 + 4.32533711e-001 3.01409870e-001 + <_> + -1.44958591e+000 -2.78536916e-001 1.18792224e+000 2.41189694e+000 + -8.12715232e-001 1.66586196e+000 + <_> + -1.59776545e+000 -7.12130427e-001 3.44747484e-001 2.07795048e+000 + 3.95791322e-001 5.46753526e-001 + <_> + -7.51333117e-001 4.01703060e-001 -9.13406670e-001 -4.20702368e-001 + 3.67698908e-001 3.00025880e-001 + <_> + -1.68779314e+000 1.35657623e-001 4.51910973e-001 1.25622165e+000 + 3.94848377e-001 1.28926885e+000 + <_> + -1.15108967e-001 3.31283540e-001 -4.83740360e-001 -6.19105101e-001 + -1.73960760e-001 4.93703574e-001 + <_> + -2.69318998e-001 3.94633800e-001 1.76321191e-003 1.27031982e+000 + -1.75190732e-001 4.39323068e-001 + <_> + -2.83103138e-001 6.05622947e-001 6.89548910e-001 1.29852450e+000 + 8.30891609e-001 -4.23226535e-001 + <_> + -1.87689424e+000 -1.44264197e+000 -1.87663484e+000 1.72850907e+000 + 7.19787180e-001 -6.60443753e-002 + <_> + -1.87086368e+000 -9.56207514e-001 9.79859948e-001 2.70000196e+000 + 8.70474875e-001 -6.40976802e-002 + <_> + -1.50041497e+000 1.13894379e+000 1.91466618e+000 3.56886029e-001 + 2.31637049e+000 -2.05700040e+000 + <_> + -1.00935507e+000 4.14002806e-001 1.82265115e+000 -2.81789017e+000 + 1.76230407e+000 -1.73007262e+000 + <_> + -8.19392323e-001 1.76330245e+000 2.53412271e+000 -3.60053241e-001 + 2.22208667e+000 -1.89913106e+000 + <_> + -1.61111879e+000 2.03761554e+000 6.76641643e-001 5.66458166e-001 + 1.34231651e+000 -1.18540251e+000 + <_> + -7.14718997e-001 1.41711378e+000 1.67323828e+000 -3.25187594e-001 + 2.17694235e+000 -1.08377683e+000 + <_> + -1.65507227e-001 1.94002676e+000 -2.41138563e-001 2.60661989e-002 + 2.11855602e+000 -1.60753310e+000 + <_> + 5.51268041e-001 2.79157686e+000 7.79279649e-001 1.53101718e+000 + 1.16594270e-001 1.25497389e+000 + <_> + -8.06486160e-002 2.55516744e+000 -3.79916906e-001 1.16915143e+000 + 7.47009456e-001 3.90299037e-002 + <_> + -6.94042802e-001 1.19477212e+000 -2.15375018e+000 4.38901097e-001 + -5.70838928e-001 1.45513511e+000 + <_> + -8.71944308e-001 1.91185081e+000 -8.01934063e-001 6.44081175e-001 + -1.92282706e-001 1.65022612e+000 + <_> + -3.90360981e-001 2.86016273e+000 -3.36805195e-001 2.20875430e+000 + -8.59496742e-002 2.66330838e+000 + <_> + -4.71342802e-001 2.97498107e+000 4.79063280e-002 2.76369452e+000 + 5.76097369e-001 3.02155209e+000 + <_> + 7.89044440e-001 2.97821641e+000 2.81087995e-001 2.47958755e+000 + 2.38765702e-001 8.15460682e-001 + <_> + -2.34858662e-001 2.83603406e+000 1.11704275e-001 2.71873069e+000 + 7.54151821e-001 3.19967175e+000 + <_> + -8.10777247e-001 3.04233074e+000 -9.29743350e-001 2.04190755e+000 + 1.60794199e-001 2.60719752e+000 + <_> + 1.81680784e-001 2.05769157e+000 8.48001063e-001 2.02963686e+000 + 7.59504855e-001 -9.51516256e-002 + <_> + -1.57105875e+000 1.53019559e+000 -2.20951247e+000 9.87439901e-002 + 9.77607548e-001 -6.81580067e-001 + <_> + -1.31045234e+000 2.03303471e-001 -2.25057936e+000 -6.33458436e-001 + 9.96864438e-001 -8.39822471e-001 + <_> + -9.96432424e-001 1.30324888e+000 5.21304488e-001 1.21571088e+000 + -4.11640614e-001 2.18854165e+000 + <_> + -1.01581633e+000 1.16322017e+000 1.90838706e+000 5.68173468e-001 + 1.02040303e+000 -9.56910569e-003 + <_> + -1.24585561e-001 3.16696823e-001 1.67149276e-001 2.95187950e-001 + -1.10444283e+000 1.34350920e+000 + <_> + -1.30571401e+000 1.30166936e+000 3.42595786e-001 1.53800339e-001 + 3.72400373e-001 1.86758590e+000 + <_> + -1.46078563e+000 1.53463149e+000 3.63518000e-002 -3.01872045e-001 + 2.58466423e-001 -6.85890734e-001 + <_> + -1.51506066e+000 2.49675080e-001 -6.47950172e-002 -4.19634670e-001 + 6.12131119e-001 1.75323591e-001 + <_> + -1.07913721e+000 1.58658713e-001 1.60340643e+000 -7.67055809e-001 + -9.17584375e-002 7.70251513e-001 + <_> + -1.47672343e+000 1.95082456e-001 4.37622130e-001 -4.46584255e-001 + -2.32429691e-002 8.78418684e-001 + <_> + -1.67099369e+000 5.77601492e-001 2.00973526e-001 -9.10931051e-001 + -3.47411484e-001 1.40652871e+000 + <_> + -1.55382848e+000 5.73083043e-001 -1.58454418e+000 -1.40367758e+000 + -4.34490621e-001 1.57922244e+000 + <_> + -1.43752480e+000 6.09640360e-001 -1.92650497e-001 -1.13888514e+000 + -4.18217659e-001 1.45663238e+000 + <_> + -1.34792793e+000 6.90211415e-001 1.34556973e+000 -1.81906855e+000 + 7.52677679e-001 -5.20089686e-001 + <_> + -1.56588960e+000 -2.68512815e-002 1.16578805e+000 1.01137304e+000 + 5.98960519e-001 1.44387436e+000 + <_> + -1.59690392e+000 1.57424903e+000 2.16886237e-001 1.39954603e+000 + 1.52755475e+000 7.12529838e-001 + <_> + -6.90182522e-002 -1.01734437e-001 1.25052893e+000 2.32963943e+000 + 1.80933928e+000 -5.20791411e-001 + <_> + -1.17476475e+000 2.63889480e+000 8.53360116e-001 2.68079877e-001 + 1.03618252e+000 -6.06437385e-001 + <_> + -1.54607499e+000 2.31760025e+000 -8.03414211e-002 -2.56123334e-001 + 6.70778215e-001 -3.44524771e-001 + <_> + -1.58570433e+000 2.57751989e+000 1.06261647e+000 -1.02664804e+000 + 2.07973883e-001 -7.85756409e-002 + <_> + -1.74551415e+000 2.51763463e+000 1.78775907e-001 1.30198276e+000 + 8.83120775e-001 1.89837670e+000 + <_> + -1.65462506e+000 2.45038128e+000 1.22780645e+000 1.40571082e+000 + 1.62255001e+000 1.23490834e+000 + <_> + -1.47155440e+000 2.30216885e+000 1.59179997e+000 1.77821875e+000 + 1.49433565e+000 6.35645330e-001 + <_> + 1.34385598e+000 3.00692391e+000 1.88233602e+000 6.39886737e-001 + 1.08072150e+000 1.33643961e+000 + <_> + -1.62188768e+000 2.76601911e+000 2.38082480e+000 9.40000296e-001 + 1.92527974e+000 1.55445039e+000 + <_> + 9.88052905e-001 2.93341136e+000 1.64651468e-001 1.59391654e+000 + 1.27622831e+000 -3.93631369e-001 + <_> + -1.72785330e+000 2.58745837e+000 -1.36370158e+000 -2.07072422e-002 + 1.88683844e+000 1.10907376e+000 + <_> + 9.96667981e-001 3.20131779e+000 2.03043365e+000 5.79823516e-002 + 1.49649727e+000 7.40817428e-001 + <_> + -1.37938946e-001 5.83963394e-001 1.52348232e+000 -5.27473509e-001 + 1.74195206e+000 -1.72435808e+000 + <_> + -2.18490005e-001 2.20571682e-001 2.88988113e-001 -5.87815225e-001 + 1.51477575e+000 -1.65263665e+000 + <_> + -8.62484202e-002 7.62939632e-001 2.46445227e+000 -1.98369384e+000 + 1.17209017e+000 -1.14304841e+000 + <_> + -1.81572711e+000 2.59680128e+000 1.46341085e+000 4.86079693e-001 + 1.85836768e+000 -8.71012032e-001 + <_> + -5.61370432e-001 2.72471809e+000 1.15676308e+000 -4.94370550e-001 + 6.04191959e-001 8.45102727e-001 + <_> + -8.41791511e-001 1.48405945e+000 -2.19219518e+000 -1.02038038e+000 + -6.83138743e-002 1.36608338e+000 + <_> + -4.13621724e-001 1.82273817e+000 1.19475222e+000 1.78414077e-001 + 2.56316602e-001 2.47026205e+000 + <_> + 1.48081943e-001 2.77959776e+000 -2.60019213e-001 -7.33976603e-001 + 1.17312372e+000 1.16598499e+000 + <_> + -1.02658522e+000 1.66331458e+000 -6.98055804e-001 2.73196131e-001 + -1.24704862e+000 2.61165714e+000 + <_> + -9.99447703e-001 1.70060468e+000 -5.42130232e-001 1.89394161e-001 + -3.45306367e-001 2.39070988e+000 + <_> + -1.06578386e+000 1.58638978e+000 -7.28219867e-001 2.18259364e-001 + -1.37938368e+000 1.76829982e+000 + <_> + -8.91328335e-001 1.64995003e+000 1.85527522e-002 -1.89069852e-001 + -3.14992033e-002 2.20757890e+000 + <_> + -1.31475985e+000 2.40033793e+000 3.49500291e-002 6.43334687e-001 + -1.07483733e-002 2.03284287e+000 + <_> + -4.39036220e-001 1.17659140e+000 2.72639871e-001 -3.46116126e-001 + -1.67309344e+000 1.80448067e+000 + <_> + -8.90897572e-001 2.51809335e+000 -1.75944611e-001 4.91873235e-001 + 5.43712378e-001 2.01436472e+000 + <_> + -4.72635061e-001 2.52702332e+000 4.13866997e-001 9.17889029e-002 + 9.24366653e-001 7.11670041e-001 + <_> + -8.46960604e-001 3.64213496e-001 -5.02179742e-001 9.87775564e-001 + 5.45325518e-001 -3.57488155e-001 + <_> + -1.93937004e-001 2.80384749e-001 9.19898152e-001 1.27188623e+000 + 3.73491913e-001 -4.45231237e-002 + <_> + -4.47651297e-001 2.50378400e-001 -5.04106462e-001 3.56784135e-001 + 9.89997566e-001 -6.97128236e-001 + <_> + -1.34318960e+000 7.39773154e-001 1.05416492e-001 1.15951145e+000 + -1.29965246e+000 2.31233072e+000 + <_> + -2.98196040e-002 3.55238587e-001 -3.41218174e-001 -8.10021639e-001 + -4.23459411e-001 3.63401204e-001 + <_> + -2.33582873e-002 3.23233277e-001 -1.25671163e-001 -2.42137337e+000 + -7.43995309e-001 4.26108152e-001 + <_> + 2.95830697e-001 1.07380971e-002 -5.16056120e-001 -6.89007521e-001 + -1.16235638e+000 1.66350424e+000 + <_> + -2.29275338e-002 2.48566285e-001 -5.98838568e-001 -1.23070620e-001 + -1.07276499e-001 5.86027727e-002 + <_> + 3.43644440e-001 -3.34886536e-002 -8.19937885e-001 -1.35922503e+000 + -1.01585877e+000 1.39152324e+000 + <_> + -6.25569299e-002 8.02983269e-002 -1.00773787e+000 -1.37149525e+000 + -7.24928796e-001 4.37570512e-001 + <_> + -1.50256872e+000 1.44443667e+000 -2.09365582e+000 -9.35393214e-001 + 1.26434934e+000 -9.14253950e-001 + <_> + -1.06363010e+000 1.28637612e+000 -2.30411828e-001 -2.27291417e+000 + 9.03292298e-001 -1.24010730e+000 + <_> + -8.83574784e-001 1.13115799e+000 1.72041059e-001 -2.44459867e+000 + 3.85919750e-001 -2.53984362e-001 + <_> + -1.25359273e+000 9.83620942e-001 -7.55770743e-001 -1.68129754e+000 + 4.58103091e-001 -1.14730251e+000 + <_> + -9.90401804e-001 1.33599901e+000 -1.57182145e+000 -1.23833919e+000 + 2.41261989e-001 -1.76904842e-001 + <_> + -4.71342802e-001 1.71482062e+000 -1.18839347e+000 -8.43926847e-001 + -6.59880579e-001 1.46688175e+000 + <_> + -9.83509779e-001 1.24156821e+000 -1.58602381e+000 -2.51949000e+000 + 7.80290902e-001 -4.96171445e-001 + <_> + -1.54047513e+000 1.94791055e+000 1.51855588e+000 -7.55823851e-001 + -2.07512945e-001 1.35236108e+000 + <_> + -1.70676276e-001 4.91704643e-001 2.08858705e+000 -2.81543231e+000 + 1.10345757e+000 -1.33630764e+000 + <_> + -1.53616762e+000 1.49554896e+000 2.00975013e+000 -2.25979114e+000 + 1.36639559e+000 -1.09481597e+000 + <_> + -1.38454211e+000 7.42015421e-001 -4.43282217e-001 -2.49167180e+000 + 8.44477236e-001 -1.12057710e+000 + <_> + -9.23204184e-001 6.36357248e-001 2.96723813e-001 -1.32392621e+000 + 1.75428891e+000 -1.64990377e+000 + <_> + -1.75154471e+000 1.29057598e+000 3.04266274e-001 -8.55901539e-001 + 2.09771919e+000 -1.42812920e+000 + <_> + -1.09205985e+000 6.18128598e-001 4.26799655e-002 -4.62456989e+000 + 1.68591177e+000 -1.46342456e+000 + <_> + -1.77179027e+000 1.19836998e+000 -2.33273059e-001 -3.67712164e+000 + 1.79195940e+000 -1.33365190e+000 + <_> + -1.79332793e+000 1.62829745e+000 6.55165076e-001 -1.54835272e+000 + 1.56059468e+000 -1.13377607e+000 + <_> + -1.79634321e+000 -9.00290608e-001 6.53053761e-001 1.61344171e+000 + -1.85457325e+000 1.71537542e+000 + <_> + -1.42330992e+000 1.13092911e+000 -1.03344572e+000 -8.91337812e-001 + -1.28652596e+000 9.64106083e-001 + <_> + -1.45432425e+000 1.28370583e+000 -3.73482019e-001 6.81874231e-002 + 9.45506036e-001 3.65494162e-001 + <_> + -9.00804937e-001 2.17508674e+000 8.40002894e-001 -1.35601497e+000 + 2.20122278e-001 -7.77294755e-001 + <_> + -2.05567375e-001 3.64177465e-001 2.12248281e-001 9.08006191e-001 + 2.27546990e-001 -8.94193053e-002 + <_> + -1.24196231e+000 1.14406729e+000 -1.76334512e+000 1.27494490e+000 + 5.94433546e-001 -2.67091364e-001 + <_> + -3.98114562e-001 2.25236893e+000 -5.71394384e-001 4.92420167e-001 + 7.78167069e-001 -5.81512630e-001 + <_> + -1.47887731e+000 6.83521450e-001 9.76064205e-001 2.44011179e-001 + -3.41323055e-002 1.83415306e+000 + <_> + -1.79117417e+000 1.10435748e+000 -1.37418890e+000 -6.95991740e-002 + 2.74325699e-001 2.04546049e-001 + <_> + -1.68865466e+000 9.14989769e-001 1.90312028e+000 -1.02978155e-001 + 8.70400518e-002 1.14835477e+000 + <_> + -1.77006721e+000 2.70431250e-001 -3.13497090e+000 -2.32301429e-001 + -5.74254215e-001 8.64306986e-001 + <_> + -1.84286463e+000 8.57181072e-001 1.57444215e+000 -7.88568407e-002 + 2.77477741e-001 4.04302418e-001 + <_> + -1.89198717e-001 6.69112206e-001 1.63504434e+000 -3.98710340e-001 + -5.08835435e-001 1.22927034e+000 + <_> + -1.07353747e+000 1.07058322e+000 1.77366698e+000 -1.62847292e+000 + -2.47951858e-002 1.05563536e-001 + <_> + -1.76619041e+000 1.34340990e+000 -2.04493463e-001 -5.00040054e-001 + 1.39685774e+000 -1.34502566e+000 + <_> + -1.34491265e+000 9.49461162e-001 1.67245662e+000 -1.23565626e+000 + 1.79699004e+000 -1.51497114e+000 + <_> + -7.77609110e-001 1.59194815e+000 1.54469347e+000 -5.66214621e-001 + 2.81349897e+000 -2.31577086e+000 + <_> + -1.41641784e+000 3.44774663e-001 7.93274283e-001 1.10532653e+000 + -3.27678114e-001 1.50605881e+000 + <_> + -8.74528885e-001 7.12818921e-001 3.83881748e-001 -1.40915477e+000 + -2.27137119e-001 9.16838884e-001 + <_> + -1.08344471e+000 9.00038719e-001 -3.91970426e-001 -1.94353712e+000 + -3.77268821e-001 1.04747188e+000 + <_> + -1.60354637e-002 -5.74121363e-002 1.41402864e+000 -2.55151540e-001 + 5.09191453e-001 -6.43679857e-001 + <_> + -6.16076231e-001 -1.05119020e-001 6.47469759e-001 -2.27150440e-001 + 3.14872265e-001 -2.72584200e-001 + <_> + 8.64840671e-002 -1.23495355e-001 1.59850311e+000 -2.93090791e-001 + -2.56762743e-001 4.32307124e-002 + <_> + 7.63955351e-004 -2.12708786e-001 1.44137478e+000 2.08153501e-001 + -2.20963106e-001 2.18733355e-001 + <_> + 2.29925275e-001 -1.01993337e-001 1.42868388e+000 1.05777858e-002 + 1.91038884e-002 2.42770351e-002 + <_> + -2.76658311e-002 -1.71652019e-001 1.15618622e+000 1.68231040e-001 + 4.58287150e-001 -7.92642161e-002 + <_> + -2.49504313e-001 4.03470665e-001 -5.05724549e-001 1.52446672e-001 + -4.97985691e-001 7.25143075e-001 + <_> + -1.30054498e+000 7.89998174e-001 -7.36543953e-001 1.41321301e+000 + -8.84624958e-001 6.55702472e-001 + <_> + -1.62834907e+000 7.87807882e-001 -8.83107066e-001 -1.07252002e+000 + -1.49803990e-002 -2.51406491e-001 + <_> + -1.45777035e+000 7.52861738e-001 -5.51065803e-001 1.82164049e+000 + -9.54577208e-001 1.16435742e+000 + <_> + -4.33005661e-001 4.86175627e-001 1.74238905e-001 1.35228825e+000 + -1.43732810e+000 2.14565992e+000 + <_> + -1.47327745e+000 6.56006396e-001 -6.04713917e-001 -2.56564051e-001 + 2.18449664e+000 -1.90887105e+000 + <_> + -9.35696006e-001 5.40086210e-001 -1.08968389e+000 1.31770158e+000 + 1.16738677e+000 -3.79244268e-001 + <_> + -9.64125812e-001 5.46278477e-001 -1.26143563e+000 5.25700390e-001 + 1.35091615e+000 -6.18658781e-001 + <_> + -1.46810842e+000 1.20549631e+000 -1.23064566e+000 2.75144666e-001 + 1.31004536e+000 -1.34585524e+000 + <_> + -1.71536136e+000 3.28502923e-001 -2.70884931e-002 1.36793524e-001 + 3.68258581e-002 -2.84257412e-001 + <_> + 1.07160278e-001 2.91718245e-001 2.45643401e+000 3.32669854e-001 + 5.34715176e-001 -5.49432397e-001 + <_> + -1.58225834e+000 1.55770111e+000 1.82931960e+000 -1.24617755e-001 + 1.20642531e+000 -1.17006445e+000 + <_> + -1.60724211e+000 -3.58740091e-001 1.62629342e+000 1.12880576e+000 + -5.75873911e-001 1.14708865e+000 + <_> + -1.67874730e+000 -7.82544851e-001 2.15588069e+000 1.79932117e+000 + -3.27763408e-002 5.43562770e-001 + <_> + -1.52755249e+000 4.08596769e-002 1.03572893e+000 6.82047367e-001 + 5.66915572e-001 -1.28818870e-001 + <_> + -1.64730227e+000 -3.70289087e-001 1.41624093e+000 1.34440732e+000 + -8.03602040e-001 1.16361833e+000 + <_> + -1.51376832e+000 -7.47978210e-001 2.82240605e+000 1.21372449e+000 + 1.23428631e+000 -1.35406768e+000 + <_> + -1.35352778e+000 2.06272793e+000 -3.52204502e-001 -1.66248441e-001 + 2.17008638e+000 -1.41443443e+000 + <_> + -1.81917322e+000 1.15145302e+000 1.29960537e+000 -4.30805057e-001 + 1.68555367e+000 -7.91709960e-001 + <_> + -1.66539395e+000 5.96926212e-001 -3.16802531e-001 3.53637815e-001 + 6.72609627e-001 -5.39866269e-001 + <_> + -1.74939096e+000 7.00706005e-001 -1.29727304e+000 -9.41479385e-001 + 9.45124984e-001 -7.30100870e-001 + <_> + -1.75714457e+000 7.03466713e-001 -1.06927741e+000 -1.29434049e+000 + 8.44964862e-001 -6.90051079e-001 + <_> + -8.00869882e-001 2.42374659e+000 1.31209731e+000 -4.15936559e-001 + 1.05940664e+000 -1.54493904e+000 + <_> + -2.02121347e-001 1.69197655e+000 1.67948282e+000 7.64394879e-001 + 1.03317523e+000 -1.51141310e+000 + <_> + -3.58054399e-001 1.34921110e+000 9.85818982e-001 4.63758677e-001 + 6.62119389e-001 -1.22565317e+000 + <_> + -8.26284349e-001 7.39629269e-001 1.69070852e+000 2.47833818e-001 + 1.91804814e+000 1.63147402e+000 + <_> + -1.17131865e+000 1.02301158e-001 9.64371264e-001 -6.82456344e-002 + -2.36798912e-001 -3.28247607e-001 + <_> + -1.37248099e+000 -7.04409122e-001 -3.59115824e-002 5.36548756e-002 + -1.79140806e+000 8.71514440e-001 + <_> + -1.48964608e+000 1.56026089e+000 2.23063326e+000 6.79536700e-001 + 9.65413600e-002 1.71590424e+000 + <_> + -1.81788099e+000 -1.17020285e+000 1.02928829e+000 1.80178547e+000 + -4.11943585e-001 2.79634643e+000 + <_> + -9.75325406e-001 8.33716989e-001 -1.51120782e+000 -3.86721230e+000 + -1.78525877e+000 7.96334445e-001 + <_> + -1.44400254e-001 5.41450799e-001 3.97169739e-001 -3.25343323e+000 + -2.31322074e+000 1.78103769e+000 + <_> + -1.39186490e+000 1.12851036e+000 -3.90147537e-001 -2.28366613e+000 + -1.15148640e+000 3.66763920e-001 + <_> + -5.42417288e-001 6.25196755e-001 -1.42247784e+000 -2.52886605e+000 + -1.24559557e+000 1.16848779e+000 + <_> + -2.11167186e-001 2.63893247e-001 -3.77465435e-003 -3.77558541e+000 + -1.16823816e+000 7.63544083e-001 + <_> + -1.64645717e-001 2.63208508e-001 4.43690687e-001 -2.54695415e+000 + -2.40795135e+000 1.77954483e+000 + <_> + -1.34749722e+000 1.16087747e+000 5.11549771e-001 -2.25911573e-001 + -1.67221272e+000 2.91098142e+000 + <_> + -3.94668519e-001 2.54493177e-001 -5.67662120e-001 -2.82837892e+000 + -1.91858697e+000 1.63429964e+000 + <_> + -1.41815737e-001 6.98571205e-001 2.65287757e-001 -3.30226392e-001 + -2.86680484e+000 2.48288059e+000 + <_> + -1.26350009e+000 1.04270899e+000 -1.56461358e+000 -3.11536741e+000 + -8.94648910e-001 3.47310126e-001 + <_> + -1.29925275e+000 9.64523852e-001 -1.50258660e+000 -1.36975884e+000 + -1.04050171e+000 1.97201037e+000 + <_> + -1.26134634e+000 9.98439312e-001 -2.57047462e+000 -1.04495633e+000 + -6.69426203e-001 1.96738446e+000 + <_> + -1.43063283e+000 1.47094893e+000 -2.19554949e+000 -1.13345885e+000 + -8.76980364e-001 1.11575699e+000 + <_> + -9.83509779e-001 1.38474143e+000 -8.71292949e-001 -1.66086507e+000 + -2.25516513e-001 7.42083848e-001 + <_> + -1.13987362e+000 5.25506377e-001 -6.97121263e-001 -2.77093697e+000 + -7.28686690e-001 8.24292004e-001 + <_> + -1.72656107e+000 1.02250826e+000 -2.51697987e-001 -7.00666845e-001 + -6.41689718e-001 8.15269113e-001 + <_> + -9.62402821e-001 7.85807550e-001 -1.60648501e+000 -2.99305797e+000 + -4.28930640e-001 4.72130179e-001 + <_> + -1.84803379e+000 1.03591001e+000 -1.15668917e+000 -2.09995008e+000 + -1.25792098e+000 1.25845468e+000 + <_> + -1.64385617e+000 1.52556729e+000 -5.87644577e-001 -5.26312232e-001 + 6.10351384e-001 -8.06083858e-001 + <_> + 8.51753354e-003 -9.21048410e-003 -7.68721430e-003 -9.44851398e-001 + 4.57655728e-001 -4.55652803e-001 + <_> + -3.69684756e-001 5.39801121e-001 4.52133060e-001 -2.45751548e+000 + -1.47828078e+000 8.45367670e-001 + <_> + -1.59476668e-001 2.58384609e+000 1.42950881e+000 1.42000671e-002 + 5.31279564e-001 1.89998671e-001 + <_> + -1.04252315e+000 1.54633498e+000 7.04524219e-001 -5.43587744e-001 + -1.28512549e+000 1.01635575e+000 + <_> + -4.48512822e-001 3.01043123e-001 -1.35071635e+000 -2.87488079e+000 + -8.53476822e-001 4.93345708e-001 + <_> + -2.30120361e-001 1.98866695e-001 -8.94880772e-001 -2.57244253e+000 + -1.27222180e+000 9.31267083e-001 + <_> + 5.36191642e-001 -2.94426024e-001 4.64226961e-001 -1.35370627e-001 + 4.82413590e-001 6.71473294e-002 + <_> + 1.60403156e+000 -7.06647992e-001 1.03766665e-001 -4.46785420e-001 + -4.59242791e-001 -1.66291809e+000 + <_> + 8.68303239e-001 1.02357768e-001 1.08071256e+000 -3.34445536e-001 + 2.05085412e-001 -3.53351980e-001 + <_> + 9.00178969e-001 4.95125800e-002 9.60512042e-001 -1.93746090e-001 + 1.16714805e-001 -3.18114102e-001 + <_> + 1.34299445e+000 1.68511625e-002 -3.42242643e-002 -4.10820931e-001 + -5.41255057e-001 3.09824944e-001 + <_> + 1.43172979e+000 1.53837055e-001 8.01038295e-002 -3.87847036e-001 + -2.26450652e-001 2.36313090e-001 + <_> + -3.84761184e-001 -7.49860257e-002 -1.19167864e+000 + -6.15461349e-001 5.84356844e-001 -1.02081430e+000 + <_> + 1.36582446e+000 3.40992324e-002 5.51560819e-001 -9.86154899e-002 + -1.97388366e-001 -2.57461667e-002 + <_> + 1.17586172e+000 -3.58402953e-002 1.74653698e-002 -4.22671974e-001 + 2.06239179e-001 -1.02509588e-001 + <_> + 1.49375844e+000 6.19325005e-002 7.27310181e-001 -4.15840715e-001 + -1.49997488e-001 5.43210357e-002 + <_> + -1.17693491e-001 6.03083484e-002 -1.46784276e-001 1.08328497e+000 + 2.22333223e-001 7.60351121e-001 + <_> + -1.81445137e-001 -2.48652324e-001 4.71794829e-002 6.65326774e-001 + 3.02082658e-001 6.42181858e-002 + <_> + -1.78429857e-001 -1.37993917e-001 3.26691747e-001 7.06002474e-001 + 5.11608943e-002 1.65270150e-001 + <_> + -9.23204184e-001 -1.32469371e-001 7.85244346e-001 1.11378860e+000 + -2.18725204e-001 1.25965834e+000 + <_> + -1.50299954e+000 3.83410975e-002 -1.02389053e-001 5.21555126e-001 + 6.11796975e-001 -2.61498392e-001 + <_> + -3.18855762e-001 -7.30728405e-003 9.69779313e-001 1.56620646e+000 + 2.87783463e-002 1.21453747e-001 + <_> + -4.62727696e-001 -1.64939240e-001 8.08532238e-001 1.03952551e+000 + 7.16244045e-004 3.66913825e-001 + <_> + -1.29494512e+000 6.53251410e-002 1.02014506e+000 1.20155048e+000 + -1.40228897e-001 1.74172115e+000 + <_> + -1.49524593e+000 2.26143435e-001 5.90986431e-001 9.89940584e-001 + 2.90027237e-003 5.18400490e-001 + <_> + -7.28072405e-001 1.79978579e-001 6.16685569e-001 8.47022176e-001 + -5.05830228e-001 6.97788119e-001 + <_> + -7.31949151e-001 -3.17937844e-002 9.20711517e-001 1.37896478e+000 + 1.42197895e+000 3.31801623e-001 + <_> + -1.25919259e+000 -5.92861092e-003 1.89915106e-001 1.03564978e+000 + -2.60481358e-001 8.43172789e-001 + <_> + -7.36256719e-001 4.65703979e-002 5.40712953e-001 2.09803724e+000 + -2.83094406e-001 2.93077886e-001 + <_> + -1.63481033e+000 7.74897337e-002 3.83945197e-001 1.19004190e+000 + 5.46967149e-001 -2.68087715e-001 + <_> + -1.82305002e+000 4.48295563e-001 -1.86795369e-001 6.42624617e-001 + 5.42544007e-001 -4.50291723e-001 + <_> + -1.78212833e+000 4.85542566e-001 3.51508290e-001 3.99561465e-001 + 5.00599623e-001 -5.91130912e-001 + <_> + 6.36540875e-002 5.09831402e-003 1.76759250e-002 6.65589795e-002 + -8.75263989e-001 7.32889056e-001 + <_> + -1.33631393e-001 -5.40322475e-002 -3.62054408e-001 + -2.35089585e-001 -2.51918495e-001 1.70459390e-001 + <_> + -8.53869170e-002 -9.07574072e-002 7.32968673e-002 3.14637423e-002 + -4.75949377e-001 1.75894320e-001 + <_> + -1.50861576e-001 -6.03438020e-002 -1.68139681e-001 + -1.82972655e-001 -2.86745548e-001 1.01528563e-001 + <_> + -2.86118418e-001 3.52004282e-002 7.64148012e-002 -8.92128572e-002 + -4.35319722e-001 -1.49327278e-001 + <_> + -2.93441236e-001 7.92007148e-002 1.64968744e-001 3.11432797e-002 + -3.58392000e-001 -3.87959480e-001 + <_> + 2.23016720e-002 -8.16046536e-001 -1.89754605e+000 -7.67506436e-002 + 5.90505123e-001 -5.80544949e-001 + <_> + 7.04185843e-001 -2.22519898e+000 4.82530743e-001 -1.38376832e+000 + 4.34741616e-001 -8.35315585e-001 + <_> + -1.01280105e+000 -7.21199334e-001 -1.17676973e+000 1.36782742e+000 + 2.33205810e-001 -5.95518351e-002 + <_> + 6.21481001e-001 -1.89527607e+000 3.84677827e-001 -4.95903492e-001 + 7.14596450e-001 -3.46119881e-001 + <_> + 1.45068300e+000 6.44830227e-001 -1.00016654e+000 -7.57464647e-001 + -2.45088339e+000 2.14220047e+000 + <_> + 1.30250359e+000 9.73551571e-001 -1.34404492e+000 -8.99854183e-001 + -2.16771460e+000 1.72748280e+000 + <_> + 7.28738785e-001 -3.57125103e-001 -1.31522501e+000 -9.82065618e-001 + 9.80707049e-001 -7.70241678e-001 + <_> + 1.52908039e+000 1.93177366e+000 -2.44193032e-001 -1.76931274e+000 + -5.53045049e-002 -2.50118881e-001 + <_> + 1.23056757e+000 -1.20085709e-001 -1.31155038e+000 -1.04355514e+000 + -4.30002660e-001 -1.82770085e+000 + <_> + 6.36557400e-001 -1.06063735e+000 -1.21832681e+000 -6.14905119e-001 + -1.97979522e+000 1.58966351e+000 + <_> + 1.39253116e+000 -6.21422753e-002 -1.00236726e+000 -7.02568054e-001 + -1.48436397e-001 -1.67672670e+000 + <_> + 5.68928957e-001 -1.08083212e+000 -1.14772761e+000 -2.55868644e-001 + -2.44844556e+000 2.10484910e+000 + <_> + 6.30096078e-001 -1.14841998e+000 1.04749119e+000 1.90687978e+000 + 1.00204337e+000 8.70734900e-002 + <_> + 8.69595468e-001 -8.66329730e-001 3.60815942e-001 6.22634590e-001 + 9.96655166e-001 -3.91860127e+000 + <_> + 1.12029445e+000 -1.22568250e+000 -6.96036756e-001 1.02967989e+000 + 1.95787227e+000 -2.30378795e+000 + <_> + 9.26024258e-001 -1.01753211e+000 1.06317604e+000 1.05658424e+000 + 1.57332420e+000 -7.12248981e-001 + <_> + 1.14269364e+000 -1.38527715e+000 -8.53421748e-001 5.51425099e-001 + 1.90985906e+000 -1.77470756e+000 + <_> + 9.20855224e-001 -1.29253125e+000 -9.53013957e-001 1.02542555e+000 + 1.31921709e+000 -2.97112346e-001 + <_> + 1.31801069e+000 -1.62407303e+000 -1.01560342e+000 -5.34735397e-002 + 1.89511150e-001 -3.43786979e+000 + <_> + 1.34170222e+000 -1.50409639e+000 -4.35915709e-001 -3.84244263e-001 + 1.08304286e+000 -2.96489620e+000 + <_> + 7.63199151e-001 -1.24497545e+000 -1.63108814e+000 1.26822114e+000 + 2.65135378e-001 5.71813822e-001 + <_> + 1.21678340e+000 -8.87993753e-001 -7.00178623e-001 -1.02117248e-002 + 1.41433382e+000 -3.60132933e+000 + <_> + -6.84996963e-001 1.95913553e-001 -2.06123099e-001 1.20416558e+000 + 7.08602443e-002 1.02359807e+000 + <_> + -6.94904327e-001 -1.46189868e-001 9.98922348e-001 2.79316568e+000 + -1.93724263e+000 2.24244809e+000 + <_> + 1.16293919e+000 -1.35684240e+000 1.74966955e+000 2.09366608e+000 + -1.04451025e+000 7.13855624e-001 + <_> + -2.82672405e-001 -1.91274121e-001 1.20046318e+000 2.97679114e+000 + 2.29972780e-001 4.34600353e-001 + <_> + 8.33412111e-001 -1.73178065e+000 7.40422249e-001 1.25416791e+000 + -1.06317532e+000 7.50202000e-001 + <_> + 2.99276739e-001 1.16466962e-001 3.86362255e-001 1.63470972e+000 + -3.81937996e-002 1.81444451e-001 + <_> + 6.04681551e-001 -7.13354290e-001 -7.58865595e-001 9.29985642e-001 + -2.40021110e+000 2.17406082e+000 + <_> + -9.22789797e-002 1.32776082e-001 -1.03088725e+000 4.21867371e-001 + 1.38783097e-001 7.56792948e-002 + <_> + -2.03844354e-001 1.68957025e-001 -7.41741478e-001 8.94295275e-001 + 1.05096912e+000 -5.92604518e-001 + <_> + -2.80087858e-001 7.42591098e-002 -7.57670961e-004 1.90932310e+000 + 5.06331623e-001 -2.83724256e-002 + <_> + 1.15820086e+000 -2.86590162e-004 6.46866918e-001 7.29203939e-001 + 1.39945699e-002 1.78244874e-001 + <_> + -6.64337203e-002 -1.28242061e-001 -1.46790576e+000 2.82705694e-001 + 1.37229252e+000 -7.21322834e-001 + <_> + 9.33761373e-002 8.01233351e-002 1.40473545e+000 1.15148447e-001 + 2.73340583e-001 5.90967666e-003 + <_> + 9.76836830e-002 -1.41485021e-001 -6.73940122e-001 1.24712801e+000 + -3.74162763e-001 6.31759644e-001 + <_> + 4.45302457e-001 -5.99859878e-002 -1.63320231e+000 8.78942311e-001 + 9.98442411e-001 -7.10973680e-001 + <_> + 1.57430959e+000 -3.80147219e-001 3.21821046e+000 5.01501918e-001 + -6.66934788e-001 6.61900163e-001 + <_> + 9.29470301e-001 -1.59346652e+000 1.25913858e+000 -1.26623616e-001 + -9.60070074e-001 6.92465663e-001 + <_> + 8.08842629e-002 7.03851283e-002 -1.41717076e+000 -7.55721107e-002 + -1.63743436e+000 1.01381576e+000 + <_> + 1.61264670e+000 -1.22107339e+000 1.93629277e+000 1.62882078e+000 + -1.90388694e-001 5.45506597e-001 + <_> + 1.59757030e+000 -1.87382495e+000 -4.86506402e-001 1.63046467e+000 + -1.64277887e+000 1.49284732e+000 + <_> + 1.45111382e+000 -2.75750041e-001 6.92663908e-001 1.09090495e+000 + 2.88747996e-001 -4.62794632e-001 + <_> + 8.14458907e-001 -6.63505718e-002 -1.26243639e+000 1.16599822e+000 + 2.52563685e-001 -3.01770002e-001 + <_> + 7.84736872e-001 -8.20047036e-002 -1.10745692e+000 8.77865851e-001 + -1.23953983e-001 -3.49886417e-001 + <_> + 1.46446717e+000 -1.18350220e+000 3.18033069e-001 2.72508883e+000 + 2.30521321e-001 1.76412582e-001 + <_> + -1.59476668e-001 2.25005671e-002 -1.16138482e+000 -3.29964191e-001 + -5.92357874e-001 1.33304104e-001 + <_> + -1.88337207e-001 1.66931786e-002 -1.85017419e+000 3.25082503e-002 + -5.56342602e-001 -1.45364434e-001 + <_> + -9.40020010e-002 -4.51173037e-002 -1.36963165e+000 + -3.64539683e-001 -5.55829406e-001 6.64101839e-001 + <_> + 9.85468328e-001 -4.27618586e-002 -1.92360866e+000 6.67229176e-001 + -1.97924852e+000 1.56968307e+000 + <_> + 1.47566676e+000 -9.72949788e-002 2.90164888e-001 5.65317571e-001 + -1.68627393e+000 1.37027669e+000 + <_> + 8.40304196e-001 -5.34500740e-003 -4.25570235e-002 -5.60086250e-001 + -2.93084055e-001 -1.14376612e-001 + <_> + 1.52348053e+000 -3.01816821e-001 3.70507210e-001 -6.29363135e-002 + 1.60180867e-001 -1.21843839e+000 + <_> + 1.46446717e+000 -4.31017160e-001 4.92328733e-001 4.65693057e-001 + 6.41600549e-001 -1.21286809e+000 + <_> + 1.37013197e+000 -4.04108971e-001 -4.11851943e-001 7.42149591e-001 + -9.65182543e-001 5.82579255e-001 + <_> + 1.61350822e+000 -1.39617538e+000 8.91404092e-001 1.50053716e+000 + -1.52624393e+000 1.33148372e+000 + <_> + 1.59455502e+000 -1.08025944e+000 3.28883797e-001 6.30861938e-001 + -8.91692579e-001 7.01055288e-001 + <_> + 1.25813580e+000 -2.32161701e-001 7.20745504e-001 5.58383942e-001 + -5.40029824e-001 1.98952541e-001 + <_> + 1.52951109e+000 -3.34326208e-001 8.37052226e-001 1.80855381e+000 + 3.40013690e-002 -1.07349969e-001 + <_> + 1.36582446e+000 -2.52709359e-001 -1.57704264e-001 9.16043639e-001 + -4.38575894e-001 2.40151286e-001 + <_> + 9.69099700e-001 -1.61978209e+000 1.61505318e+000 6.40262187e-001 + 8.07106256e-001 -6.32842898e-001 + <_> + -1.94367766e-001 -4.66547579e-001 -8.12413245e-002 7.66581297e-001 + 2.30986643e+000 -2.15368581e+000 + <_> + 2.33802065e-001 -9.19805050e-001 -5.01473069e-001 8.41915384e-002 + 2.29130834e-001 -2.62777710e+000 + <_> + 1.29302692e+000 -1.35908604e+000 5.16905375e-002 -5.08379936e-003 + 9.49899733e-001 -1.84147501e+000 + <_> + -4.60143179e-001 -2.44357646e-001 -9.78571773e-001 2.74506927e-001 + 8.03717017e-001 -1.44857931e+000 + <_> + 8.07566881e-001 -2.03954518e-001 -1.67764708e-001 -1.37194008e-001 + -1.51199567e+000 1.39803755e+000 + <_> + 1.20816839e+000 -9.60311532e-001 -2.38955673e-002 -2.61106163e-001 + -2.09073687e+000 2.25741696e+000 + <_> + 7.35630870e-001 -1.49433702e-001 -3.45772505e-001 4.30699527e-001 + -1.65940642e+000 1.68265724e+000 + <_> + 8.93717647e-001 4.80806828e-001 -7.49993443e-001 -1.74327418e-001 + -1.19332182e+000 1.34628856e+000 + <_> + 2.31217548e-001 -2.23169118e-001 -1.73593557e+000 1.06605268e+000 + -8.07997763e-001 9.27501500e-001 + <_> + 3.74658734e-001 -1.05626345e+000 -3.29548270e-001 1.66904342e+000 + -1.01515329e+000 1.33368504e+000 + <_> + 8.18766475e-001 -2.49414712e-001 -8.28227341e-001 1.58177531e+000 + -8.33626628e-001 1.26443112e+000 + <_> + 4.99146760e-001 7.46985972e-002 -2.07170099e-001 -1.23834670e+000 + 3.80214155e-001 -1.13959384e+000 + <_> + 6.07696831e-001 1.34665761e-002 -3.10520530e-001 -7.20683455e-001 + -9.66326222e-002 -3.77507836e-001 + <_> + 6.69294715e-001 2.06413493e-001 -6.27176881e-001 -9.51055527e-001 + -6.08612657e-001 -3.94427836e-001 + <_> + 4.12134379e-001 1.58447909e+000 -3.59144151e-001 -1.12678647e+000 + -4.94702548e-001 -9.71926510e-001 + <_> + 1.24821208e-001 6.13421261e-001 -1.15553021e-001 -9.48275208e-001 + -9.48494494e-001 2.15160996e-001 + <_> + 4.28503036e-001 1.14280546e+000 3.86486262e-001 -1.27435446e-001 + -1.32499766e+000 6.34640634e-001 + <_> + 1.51185012e+000 -1.08886337e+000 2.69937277e-001 -8.69847059e-001 + -2.30956006e+000 8.44199240e-001 + <_> + 1.20386076e+000 1.28905907e-001 3.08734059e-001 -1.03040445e+000 + -9.65167999e-001 -7.16051221e-001 + <_> + 1.37960863e+000 -2.18973130e-001 1.84642583e-001 -6.91115916e-001 + -2.07692981e+000 7.68069088e-001 + <_> + 1.35979390e+000 -4.33871835e-001 6.89825773e-001 -1.30635059e+000 + -8.38307738e-001 -1.80195987e+000 + <_> + 1.49892747e+000 -9.83512104e-001 8.25745761e-001 -5.73750973e-001 + -1.94270551e+000 8.76929283e-001 + <_> + 5.31884074e-001 3.37684937e-002 -7.05578029e-001 -1.46242440e-001 + 8.30627620e-001 -1.29899645e+000 + <_> + 9.95806456e-001 -1.90508589e-001 1.46330357e-001 -8.19759294e-002 + 1.31140494e+000 -1.31574166e+000 + <_> + 1.25382829e+000 4.10485536e-001 -1.07999551e+000 -1.11486125e+000 + 4.74945277e-001 -2.17672849e+000 + <_> + 8.76487553e-001 -7.33251631e-001 -1.24911964e+000 1.25606143e+000 + 1.37470126e+000 -1.41287637e+000 + <_> + 1.59110892e+000 -9.24826980e-001 -4.82658744e-001 2.15709662e+000 + -2.26008624e-001 4.14903522e-001 + <_> + 1.41105366e+000 -6.78475499e-001 -1.15333188e+000 -4.89777982e-001 + -8.44210446e-001 1.30368793e+000 + <_> + 7.66214430e-001 -3.17929029e-001 -1.11982203e+000 -5.75836539e-001 + 9.16159078e-002 5.35219498e-002 + <_> + 1.49074316e+000 2.24500939e-001 -3.51754546e-001 -1.75352323e+000 + -2.49815702e-001 -1.56354988e+000 + <_> + 1.53511095e+000 1.22403257e-001 2.13580817e-001 -7.79623508e-001 + -1.32654166e+000 8.98233235e-001 + <_> + 1.07937276e+000 7.90292025e-001 -3.68206620e-001 -1.99374247e+000 + 2.13712081e-001 -2.68864989e+000 + <_> + 1.33825612e+000 -1.13629651e+000 5.69423318e-001 -1.51897681e+000 + -3.64174873e-001 -1.49540913e+000 + <_> + 1.35462487e+000 -4.49938625e-001 -1.04477286e-001 -1.05751038e+000 + -6.00202084e-001 6.12421101e-003 + <_> + 1.57990932e+000 2.21658587e+000 2.45023561e+000 -2.29839087e-001 + 6.38345033e-002 4.66296852e-001 + <_> + 9.09224808e-001 -1.06685400e+000 -1.12410808e+000 -1.31592500e+000 + -1.49304223e+000 -2.57538199e+000 + <_> + 9.60484624e-001 -2.96464831e-001 -8.91145587e-001 -1.81231487e+000 + -6.33754909e-001 2.76692659e-001 + <_> + 1.30293429e+000 8.26194584e-001 -1.20840478e+000 -1.92520905e+000 + -1.15009665e+000 1.03777957e+000 + <_> + 1.53683388e+000 -1.46960032e+000 -8.51270080e-001 -2.33388264e-002 + -1.67779967e-001 -1.91289389e+000 + <_> + 1.27967358e+000 -1.96115816e+000 -1.28884804e+000 -1.05769682e+000 + -6.60350248e-002 -2.81633162e+000 + <_> + 4.99577492e-001 -1.46238852e+000 -1.23792851e+000 -2.82673061e-001 + 3.69322598e-001 -5.77848032e-002 + <_> + 1.55191028e+000 -2.45606399e+000 -1.02172101e+000 1.48072481e-001 + -7.41371214e-001 9.19744730e-001 + <_> + 7.44245946e-001 -1.26768053e+000 -2.01383543e+000 -1.73082426e-001 + -9.25628066e-001 1.12222779e+000 + <_> + 7.87321389e-001 6.07207641e-002 -1.58871484e+000 -1.19679761e+000 + -2.82184541e-001 -1.43170750e+000 + <_> + 1.15173948e+000 1.36581138e-001 -1.23176181e+000 -2.07873058e+000 + 3.95368278e-001 -2.19340181e+000 + <_> + 1.07118845e+000 -5.06452024e-002 -1.43970871e+000 -9.10561621e-001 + -2.22641301e+000 1.12885463e+000 + <_> + 7.92059720e-001 1.66220263e-001 -1.96096909e+000 -1.54500139e+000 + -1.42820269e-001 -1.92246270e+000 + <_> + 1.60575461e+000 2.27340698e+000 6.73168898e-001 -1.27021992e+000 + -5.79416931e-001 3.13983887e-001 + <_> + 1.38822365e+000 3.26187760e-001 6.04464769e-001 -1.75879943e+000 + 9.00884092e-001 -1.70906150e+000 + <_> + 1.52477276e+000 2.49273628e-001 -3.37142646e-001 -1.46869695e+000 + 3.65885608e-002 -8.41141880e-001 + <_> + 1.44594479e+000 -1.04115403e+000 -1.06792760e+000 9.23890993e-002 + -1.89258289e+000 6.82779968e-001 + <_> + 1.26115108e+000 -1.02381778e+000 -1.73940018e-001 3.26958776e-001 + -1.74631631e+000 7.70506799e-001 + <_> + 1.52951109e+000 -1.32198358e+000 -4.28281464e-002 -6.17653765e-002 + -2.14916170e-001 8.41283679e-001 + <_> + 2.10110560e-001 -1.25217688e+000 -8.17079544e-001 5.80787286e-002 + -1.11777306e+000 9.74835396e-001 + <_> + 1.40889990e+000 -1.73572671e+000 -1.97110736e+000 4.01430845e-001 + -2.77377391e+000 2.40861750e+000 + <_> + 3.40629160e-001 -1.50460434e+000 -1.80021226e+000 8.54813874e-001 + -9.81557548e-001 1.05528152e+000 + <_> + 7.99382508e-001 -1.85262823e+000 -2.23091412e+000 2.57814407e-001 + -2.33605075e+000 1.98116553e+000 + <_> + 1.44120646e+000 -1.90131199e+000 -6.12357318e-001 6.05977893e-001 + -1.83357167e+000 1.81257141e+000 + <_> + 8.76056790e-001 -1.80876720e+000 -2.73855239e-001 -2.30083853e-001 + -1.57305372e+000 1.71352148e+000 + <_> + 1.95895672e-001 -8.94602954e-001 2.00448632e+000 -7.32012928e-001 + 5.39449342e-002 -2.89806694e-001 + <_> + 1.13752460e+000 -2.07853341e+000 -4.15994339e-002 -4.71531063e-001 + 2.47552648e-001 2.96702147e-001 + <_> + 6.04681551e-001 -2.23658371e+000 1.50570345e+000 -9.77601707e-001 + 5.64462900e-001 -2.42121980e-001 + <_> + -6.81567416e-002 -1.01354206e+000 -1.16838789e+000 + -8.15028310e-001 -7.68980160e-002 2.89311111e-001 + <_> + 2.10110560e-001 -2.03573155e+000 7.16690183e-001 -8.97102118e-001 + 5.66995680e-001 -8.82632196e-001 + <_> + -2.20660251e-002 -1.46627378e+000 7.40601063e-001 6.59516752e-001 + -4.47339475e-001 -2.80032068e-001 + <_> + 1.37185502e+000 -1.45938897e+000 2.68416142e+000 5.93705893e-001 + 3.59734893e-002 -1.94741040e-001 + <_> + 2.72139192e-001 -1.00845730e+000 6.28072798e-001 -1.08699167e+000 + -1.00488520e+000 -1.82594225e-001 + <_> + 1.03758967e+000 -9.38033283e-001 -1.82741129e+000 -3.26838195e-001 + 4.76656556e-001 -1.39522481e+000 + <_> + 9.37904231e-003 -3.76823723e-001 2.78287321e-001 3.66049379e-001 + 3.40397567e-001 -1.77431375e-001 + <_> + -4.31729890e-002 -3.31527114e-001 1.55762047e-001 -7.62076437e-001 + 4.53506671e-002 -7.01706767e-001 + <_> + 9.00609732e-001 -1.10594654e+000 -1.60385740e+000 -5.38730621e-001 + -1.75713933e+000 -1.67510152e-001 + <_> + 9.29453820e-002 -2.77704716e-001 2.44711131e-001 -9.35606241e-001 + 4.00493592e-001 -3.61156940e-001 + <_> + 1.30982637e+000 -1.03487754e+000 3.15950572e-001 3.95358682e-001 + 1.70554295e-001 3.68832439e-001 + <_> + 1.31456470e+000 -1.29819810e+000 8.44620094e-002 1.08476736e-001 + 1.70275837e-001 5.73109746e-001 + <_> + 1.47049773e+000 -5.25508523e-001 -1.39602363e-001 1.13344014e+000 + -3.42401624e-001 4.77405965e-001 + <_> + 1.31757998e+000 -5.30549288e-001 -1.06428468e+000 8.32224190e-001 + 7.26815760e-001 -1.25484788e+000 + <_> + 5.44375956e-001 -1.27715802e+000 -9.45506096e-001 -2.26621225e-001 + 6.13923669e-001 -2.05752468e+000 + <_> + 1.32102597e+000 -1.41681957e+000 1.08207405e+000 1.21095903e-001 + 2.59947419e-001 -1.52610290e+000 + <_> + 1.33394861e+000 -2.18772173e+000 -9.68090236e-001 5.94230413e-001 + 2.42225826e-001 1.54413534e-002 + <_> + 1.37616253e+000 -1.79899418e+000 -1.26196051e+000 6.29715562e-001 + -8.79757404e-001 -7.96489954e-001 + <_> + 1.46877468e+000 -2.22758341e+000 1.68178976e-001 -1.96601540e-001 + -9.45381641e-001 -7.73310363e-001 + <_> + 1.47178996e+000 -2.40326715e+000 5.96192598e-001 -7.34855652e-001 + -6.23306155e-001 -1.10900426e+000 + <_> + 1.16810822e+000 -2.67006755e+000 -1.77487659e+000 1.36716202e-001 + -1.11376870e+000 -1.19851992e-001 + <_> + 1.51314247e+000 -2.05086017e+000 -8.68374202e-003 -6.66399479e-001 + -9.72546279e-001 -1.84461832e+000 + <_> + 1.53984916e+000 -1.91331255e+000 2.15818238e+000 -1.00124955e+000 + -1.27838939e-001 -2.33071804e+000 + <_> + 1.38219309e+000 -2.70599627e+000 -9.64640558e-001 3.54958653e-001 + -1.11301637e+000 1.80692494e-001 + <_> + 1.51960373e+000 -1.65374029e+000 1.16310515e-001 3.13287154e-002 + -1.57062209e+000 -7.75441706e-001 + <_> + 1.47222078e+000 -1.49389184e+000 -4.56071228e-001 -8.18010986e-001 + -8.74228656e-001 -1.03030789e+000 + <_> + 1.44077575e+000 -1.64367628e+000 -7.76898265e-001 1.12726763e-001 + -1.62760913e+000 -5.60754895e-001 + <_> + 1.57258654e+000 -1.43161416e+000 -1.44509947e+000 -2.43509188e-001 + -1.89635265e+000 1.26387179e+000 + <_> + -2.50813048e-002 -3.81590784e-001 -5.35294414e-001 + -2.94069886e-001 -1.66599965e+000 1.32699144e+000 + <_> + 1.27450454e+000 -9.23860967e-001 2.29628552e-002 -1.08032811e+000 + -1.06629395e+000 1.17715871e+000 + <_> + -3.97269540e-002 -5.72292328e-001 -5.06722510e-001 6.04756653e-001 + -1.72568452e+000 2.04283166e+000 + <_> + -1.00032561e-001 -6.33660436e-001 -5.88423312e-001 1.27477610e+000 + -1.99884951e+000 1.96468854e+000 + <_> + 7.58891642e-001 -2.28711319e+000 -7.99029529e-001 -1.05386965e-001 + -2.16390300e+000 1.56387317e+000 + <_> + 1.96757182e-001 -6.00957632e-001 1.10131812e+000 1.96363354e+000 + 1.45861399e+000 3.57287973e-002 + <_> + 1.62546407e-003 -5.79656959e-001 7.07835376e-001 6.65174305e-001 + 1.36781061e+000 -1.98477842e-002 + <_> + 7.70521998e-001 -2.74439096e+000 1.87286958e-002 -2.50160694e-001 + -2.63399482e-001 -1.02929342e+000 + <_> + 9.42376480e-002 -1.14166403e+000 1.45356679e+000 3.34114999e-001 + 1.22620010e+000 -1.69153988e+000 + <_> + -1.76276088e-001 -6.33883536e-001 1.12719023e+000 9.37922359e-001 + 1.14611053e+000 -6.50704026e-001 + <_> + -3.73992324e-001 -5.26993752e-001 1.77408803e+000 9.74284589e-001 + 1.46598697e+000 -2.03691125e-001 + <_> + 1.23013687e+000 -2.15383554e+000 2.27315888e-001 4.69180584e-001 + 7.60046422e-001 -4.80643064e-001 + <_> + 1.23098187e-001 -6.00993633e-001 6.42113626e-001 2.51303881e-001 + -5.51634669e-001 6.99464500e-001 + <_> + 6.94709241e-001 -7.51546264e-001 -1.89971793e+000 -1.28564632e+000 + 2.39470288e-001 -1.12593043e+000 + <_> + -1.23724051e-001 -1.64370641e-001 -9.82950151e-001 + -9.10351634e-001 3.34356189e-001 -5.86784303e-001 + <_> + 5.68067431e-001 -3.56827259e-001 4.89678055e-001 -1.06626272e+000 + 6.15944862e-001 -1.05041969e+000 + <_> + 4.95700717e-001 -2.94217914e-001 -4.87871207e-002 -1.10978687e+000 + 5.64338386e-001 -9.70202386e-001 + <_> + 1.49788484e-002 -3.32634121e-001 4.22496825e-001 -5.67065954e-001 + 2.75093108e-001 -9.03493822e-001 + <_> + 1.55320263e+000 -1.24420905e+000 -1.81341141e-001 3.02544445e-001 + -8.19019675e-002 6.95826769e-001 + <_> + 4.46594715e-001 -1.17014416e-001 -4.18099344e-001 3.96539181e-001 + 1.04600534e-001 8.82158816e-001 + <_> + -2.23228291e-001 1.12138048e-001 -6.34877980e-001 5.01077473e-001 + -2.16970295e-001 8.52663338e-001 + <_> + -1.48707807e-001 -2.64015555e-001 -4.20810580e-001 3.00923616e-001 + 1.59916866e+000 -1.58916533e+000 + <_> + 2.15279624e-001 1.61527637e-002 1.33547127e-001 8.53414536e-001 + 1.90576971e+000 -6.43458426e-001 + <_> + -7.33257905e-002 -2.32079804e-001 -2.64019758e-001 4.49800551e-001 + 7.59032428e-001 -8.14804256e-001 + <_> + -8.53869170e-002 -1.63073421e-001 -1.91724643e-001 1.84991586e+000 + 1.38666213e+000 -4.92028892e-001 + <_> + -3.49008560e-001 -1.79632038e-001 2.77773917e-001 9.47167695e-001 + 1.08443342e-001 1.08440019e-001 + <_> + -1.41384974e-001 7.81711265e-002 -1.38673618e-001 5.60074091e-001 + 1.12641037e+000 -2.08524242e-001 + <_> + 5.43083668e-001 -5.96113324e-001 1.77014804e+000 6.15714252e-001 + -6.13655269e-001 2.33146876e-001 + <_> + 9.09224808e-001 -1.11481440e+000 1.57565653e+000 1.12284064e+000 + -5.74045777e-001 -6.14992492e-002 + <_> + 1.22970605e+000 -1.31477642e+000 -1.87734437e+000 8.95947695e-001 + -1.99173093e+000 7.17274129e-001 + <_> + 1.25985885e+000 -1.13471675e+000 1.47033036e+000 2.09273219e+000 + -1.41453803e+000 1.25984812e+000 + <_> + 1.51615775e+000 -1.59801316e+000 -2.01550555e+000 4.66131359e-001 + -8.18321109e-001 -7.93367803e-001 + <_> + 4.36687380e-001 -6.15966797e-001 1.21075153e+000 7.98694909e-001 + -1.28197277e+000 1.39152896e+000 + <_> + 1.57603252e+000 -1.03258908e+000 1.26456690e+000 5.75649381e-001 + -1.26626813e+000 -1.08228242e+000 + <_> + 1.35591710e+000 -1.02365708e+000 8.82578135e-001 6.35696292e-001 + -1.25902092e+000 -1.04559255e+000 + <_> + 1.30164206e+000 -1.55954874e+000 4.62412715e-001 3.25395614e-001 + -8.32527697e-001 -2.25227761e+000 + <_> + 5.32314837e-001 -4.00860220e-001 -2.95372698e-002 2.24926996e+000 + -6.21300757e-001 5.81376195e-001 + <_> + 1.51012719e+000 -7.03601301e-001 2.86634505e-001 1.62823880e+000 + -1.27376413e+000 7.96040595e-001 + <_> + 6.07696831e-001 -1.17491579e+000 -1.09832239e+000 8.61721456e-001 + -2.47197700e+000 1.70349669e+000 + <_> + 1.16724670e+000 -6.37061119e-001 -7.83151448e-001 2.10288477e+000 + -1.24855363e+000 1.05944920e+000 + <_> + 1.76511735e-001 -2.93152303e-001 -6.94623470e-001 9.22124565e-001 + -1.59828722e+000 1.66510904e+000 + <_> + 8.45042467e-001 -7.90920019e-001 -5.42182148e-001 8.69656026e-001 + -2.50739241e+000 1.88279760e+000 + <_> + 1.12804806e+000 -4.38175023e-001 2.93992370e-001 3.20751595e+000 + -1.13168776e+000 1.01252151e+000 + <_> + 4.08241078e-002 -2.31890053e-001 -2.61343122e-001 8.97402227e-001 + -1.14329219e+000 1.45432019e+000 + <_> + 1.50409663e+000 -8.68448853e-001 -7.35528648e-001 -1.74356985e+000 + -7.46207058e-001 -9.30667892e-002 + <_> + 1.14527822e+000 -1.56852412e+000 -1.78117585e+000 -1.49072266e+000 + -2.79471904e-001 -1.40727568e+000 + <_> + 1.32705653e+000 -1.34602559e+000 -6.86988711e-001 -1.20566583e+000 + -1.82581797e-001 -3.14411461e-001 + <_> + 1.01519036e+000 -2.01764369e+000 3.38586628e-001 -1.82756090e+000 + 6.51322305e-003 -6.40353635e-002 + <_> + 1.22755229e+000 -2.59659791e+000 -5.49214065e-001 -1.41357327e+000 + -4.91703659e-001 4.14184868e-001 + <_> + 8.53226840e-001 -4.22335178e-001 2.48757577e+000 -9.99060631e-001 + -3.62405553e-003 6.08262466e-003 + <_> + 1.29647303e+000 -2.69000745e+000 -7.71645963e-001 -4.79154497e-001 + -5.68356156e-001 5.61865747e-001 + <_> + 1.28225815e+000 -2.72888303e+000 -8.52983356e-001 -1.41567969e+000 + -9.70240474e-001 4.34906811e-001 + <_> + 1.58766294e+000 -1.14220667e+000 -1.16948104e+000 2.00699970e-001 + -8.15583706e-001 -1.12800017e-001 + <_> + 1.49505079e+000 -6.29047751e-001 -4.69039053e-001 3.30631495e-001 + -7.58467317e-001 -1.28236580e+000 + <_> + 1.50754261e+000 -6.93333864e-001 -6.78344429e-001 9.01882946e-002 + 2.12571070e-001 -2.14269781e+000 + <_> + 1.61178517e+000 -1.04438221e+000 -9.31240320e-001 1.03690529e+000 + -3.97205323e-001 -1.19760819e-001 + <_> + 1.55535638e+000 1.21197522e+000 -1.07081187e+000 -1.02693510e+000 + 6.49988890e-001 -4.29152429e-001 + <_> + 1.59972405e+000 -8.68061304e-001 -5.38712323e-001 5.01359224e-001 + 5.52729547e-001 -8.12592030e-001 + <_> + 1.59972405e+000 -6.63681984e-001 -1.40190750e-001 6.89420164e-001 + 5.45043349e-001 -7.78626621e-001 + <_> + 1.52951109e+000 -6.73251033e-001 2.40144467e+000 1.15599550e-001 + -2.13159680e+000 1.71851397e+000 + <_> + 1.48945093e+000 -8.08033645e-001 -1.87040472e+000 -1.68911293e-001 + -2.22779608e+000 1.58609414e+000 + <_> + 1.14140141e+000 -8.46659541e-001 -1.97470117e+000 -2.47310698e-001 + -2.81656003e+000 2.31290388e+000 + <_> + 1.43043756e+000 -8.57261479e-001 1.50345311e-001 -1.00079954e+000 + -1.55013466e+000 -2.33177972e+000 + <_> + 6.86955631e-001 -7.41928697e-001 -5.75279534e-001 -9.15930569e-001 + 2.12594569e-002 -2.05327797e+000 + <_> + 9.27316487e-001 -1.11021590e+000 -9.01572347e-001 -3.17876399e-001 + -7.34112024e-001 -1.50130033e+000 + <_> + 8.63134146e-001 -7.83381522e-001 -1.02594936e+000 -4.49502133e-002 + -1.05473709e+000 -9.37110066e-001 + <_> + 1.59281552e-001 -5.27387321e-001 -1.30966115e+000 1.50435910e-001 + -9.90347981e-001 -9.73756909e-001 + <_> + 1.32964110e+000 -5.78818738e-001 1.52321070e-001 -1.44736433e+000 + -9.47317258e-002 -2.61027312e+000 + <_> + 1.32619500e+000 -4.41832632e-001 -3.86547923e-001 -1.04904163e+000 + -9.45000470e-001 -1.67518544e+000 + <_> + 1.60101628e+000 -1.06800459e-001 9.53243613e-001 -1.32213080e+000 + -1.15981793e+000 -1.05996573e+000 + <_> + 1.30551887e+000 5.61557114e-001 -1.25990689e+000 -3.66520643e-001 + 5.43642491e-002 -2.38161579e-001 + <_> + 1.50666460e-001 1.38847977e-001 -9.93333638e-001 -4.65817839e-001 + 1.10272489e-001 -4.18847978e-001 + <_> + -4.40509664e-003 2.03890204e-001 -1.34384596e+000 -6.17502667e-002 + 2.18989119e-001 -4.34875488e-001 + <_> + 5.15084684e-001 -3.60391766e-001 2.22679710e+000 1.86889458e+000 + 2.03132093e-001 -6.59969598e-002 + <_> + 3.68628174e-001 -2.01098710e-001 1.55350792e+000 6.36582613e-001 + -4.22941893e-002 1.18192554e-001 + <_> + 3.12199354e-001 -9.52152163e-002 1.22545862e+000 6.85557485e-001 + 4.08572257e-001 -1.55073896e-001 + <_> + 4.38841134e-001 -1.54444680e-001 2.09950113e+000 1.47460926e+000 + 9.51186538e-001 -7.09011436e-001 + <_> + 7.18400717e-001 -5.83537780e-002 1.28719139e+000 -1.18293071e+000 + 6.83884799e-001 -2.87450731e-001 + <_> + 2.98415214e-001 -5.75372398e-001 1.53201985e+000 3.47082727e-002 + 4.50174958e-001 -6.41473711e-001 + <_> + 3.07030320e-001 -5.63166380e-001 1.21592307e+000 -9.14768130e-002 + 7.60108232e-001 -1.75418484e+000 + <_> + 6.95139945e-001 -7.58968174e-001 2.34550357e+000 8.51182878e-001 + 1.30367243e+000 -1.65261185e+000 + <_> + 9.39808369e-001 -1.62678921e+000 6.97728753e-001 -6.20173037e-001 + -9.68830168e-001 1.31904709e+000 + <_> + 2.63093352e-001 -7.12532818e-001 1.46585965e+000 -4.60753113e-001 + -3.35793614e-001 7.54957497e-002 + <_> + 1.44766772e+000 -1.05901562e-001 8.50628674e-001 1.34188822e-002 + -1.84726596e+000 1.19153523e+000 + <_> + 1.57904780e+000 -8.66551816e-001 -2.23114535e-001 1.53064787e-001 + -1.39053226e+000 1.62168992e+000 + <_> + 1.57172501e+000 6.79298639e-001 -4.61848497e-001 -4.74523097e-001 + -1.56277370e+000 1.73309267e+000 + <_> + 1.56741750e+000 5.63300550e-001 -1.00180769e+000 -8.02324116e-001 + -7.41727233e-001 1.01889527e+000 + <_> + 1.54157221e+000 9.50539947e-001 -1.47340894e+000 -1.24904907e+000 + -4.22942787e-001 8.45624983e-001 + <_> + 1.47480524e+000 4.71162379e-001 -1.51938474e+000 -5.55607677e-001 + -1.37104726e+000 1.49461877e+000 + <_> + 1.19739950e+000 -1.27031469e+000 -1.41771591e+000 6.77411377e-001 + -2.77323890e+000 1.57156813e+000 + <_> + 1.26115108e+000 -2.37677598e+000 -1.58998394e+000 4.95372027e-001 + -2.18436074e+000 1.53715432e+000 + <_> + 6.46034002e-001 -1.42480123e+000 -8.32398057e-001 1.90312910e+000 + 8.88998747e-001 -2.04435617e-001 + <_> + 7.86890626e-001 -1.13833189e+000 -9.20150161e-001 -1.85455334e+000 + 8.43814552e-001 -1.40644360e+000 + <_> + 6.10712111e-001 -9.91066575e-001 -8.44838083e-001 -2.07021999e+000 + 5.20711184e-001 -1.20461798e+000 + <_> + 8.30396831e-001 -1.71374750e+000 -8.29978108e-001 -9.88164485e-001 + -1.09940374e+000 1.43658412e+000 + <_> + 3.00138235e-001 -1.27561712e+000 -1.42929065e+000 -9.56515968e-001 + -4.02079403e-001 2.32780814e-001 + <_> + 1.17672324e+000 -1.09163415e+000 -1.52509570e+000 -1.59007990e+000 + -1.21480894e+000 8.48092139e-001 + <_> + 1.17844629e+000 -1.12873054e+000 -6.84032261e-001 -2.21382141e+000 + -5.50024271e-001 -8.46529007e-001 + <_> + 4.45302457e-001 -1.29563451e+000 -1.65636337e+000 -1.21239388e+000 + -2.96483219e-001 -8.99960458e-001 + <_> + 5.18099964e-001 -1.43412828e+000 -1.30136597e+000 -2.19381380e+000 + -1.80936590e-001 -6.83804870e-001 + <_> + 9.34208572e-001 -1.27557743e+000 -1.70029414e+000 -2.97504663e+000 + -7.01801777e-002 -8.12418759e-001 + <_> + 6.40434146e-001 -1.03826642e+000 -9.44606185e-001 -2.41761303e+000 + -1.11763203e+000 -7.83662438e-001 + <_> + 7.00739801e-001 -4.45400029e-001 -7.92447567e-001 2.34969296e-002 + 2.01471996e+000 -1.30585623e+000 + <_> + 2.14401633e-002 -8.91442224e-002 -1.15839672e+000 1.30951762e+000 + 1.65944004e+000 -1.41551602e+000 + <_> + -1.60338163e-001 -1.19448848e-001 -1.49317217e+000 2.76905179e-001 + 1.94465744e+000 -1.78758442e+000 + <_> + -1.54307604e-001 1.41403610e-002 -1.30685186e+000 8.98093641e-001 + 3.46542537e-001 -4.00706172e-001 + <_> + 5.85297644e-001 -1.33031332e+000 -1.00497472e+000 6.36019826e-001 + 1.58766127e+000 -2.60051942e+000 + <_> + 1.51400399e+000 -1.32061064e+000 -3.03456873e-001 1.18764579e+000 + -2.47831896e-001 1.68673277e-001 + <_> + 1.53640318e+000 -1.65227926e+000 8.29685748e-001 1.65230119e+000 + 1.22306548e-001 1.71267733e-001 + <_> + -5.39402008e-001 -1.42638206e+000 1.35873365e+000 1.35103774e+000 + -1.20558374e-001 -2.38612920e-001 + <_> + 9.41962183e-001 -1.38353598e+000 1.84253201e-001 2.12518930e+000 + -5.90030551e-002 3.53263706e-001 + <_> + 9.92360413e-001 -1.62262273e+000 -8.95284593e-001 1.53884992e-001 + 7.15965629e-001 -9.94557858e-001 + <_> + 1.51486540e+000 -1.39818358e+000 -5.02211452e-001 1.77129507e-001 + 4.80267555e-001 4.21578158e-003 + <_> + 1.04749703e+000 -1.04597604e+000 6.97867215e-001 2.73495495e-001 + -9.42068756e-001 1.52458882e+000 + <_> + -2.59428136e-002 7.21122175e-002 -5.22854447e-001 1.51278043e+000 + 2.62841642e-001 -1.38828591e-001 + <_> + 3.78088281e-002 -2.07090259e-001 7.52841532e-002 -6.11406639e-002 + 1.53517103e+000 -8.69754255e-001 + <_> + 5.93300769e-003 -2.99310118e-001 1.43091291e-001 2.64749527e+000 + 1.84106982e+000 -1.19034064e+000 + <_> + -1.11232176e-001 -2.17829406e-001 4.99248177e-001 1.66832650e+000 + 1.76545548e+000 -1.09268177e+000 + <_> + 3.99211735e-001 1.32352039e-001 -2.05254912e-001 1.84438825e+000 + 1.49144018e+000 -1.05289090e+000 + <_> + -2.15474725e-001 -1.99457318e-001 1.86831772e-001 2.36905262e-001 + 2.16349387e+000 -1.29466927e+000 + <_> + -2.10305676e-001 -1.55665368e-001 2.07933351e-001 -3.36018018e-002 + 2.16360998e+000 -1.36640155e+000 + <_> + -1.49138555e-001 -3.84300470e-001 5.27719140e-001 -5.56045175e-001 + -4.02488083e-001 -6.97377741e-001 + <_> + 1.31713271e-001 -2.30785728e-001 -8.05617273e-001 -9.32332993e-001 + -4.87858117e-001 -2.87966728e-001 + <_> + -6.21676087e-001 -2.25941896e-001 4.84457463e-001 -4.51218039e-001 + -2.11399555e-001 -5.64269006e-001 + <_> + -1.02356994e+000 -2.18885407e-001 1.01825869e+000 -1.73240587e-001 + -1.24016060e-002 4.24534418e-002 + <_> + -1.35697377e+000 -3.15601639e-002 1.36424851e+000 1.62855983e-001 + -1.27995872e+000 1.57667863e+000 + <_> + -1.30657554e+000 -1.40234560e-001 1.14936197e+000 3.26389194e-001 + 1.11835077e-001 6.30859658e-002 + <_> + -1.16356504e+000 -2.07020447e-001 6.30521595e-001 -3.41877699e-001 + 6.50350928e-001 -3.70083988e-001 + <_> + -3.43408763e-001 -1.19638830e-001 9.11340415e-001 9.13498402e-001 + 7.89959356e-002 1.16192870e-001 + <_> + -1.78083611e+000 1.39038190e-001 -2.47447634e+000 -1.42148840e+000 + 5.20611107e-001 -3.99406135e-001 + <_> + -1.77135944e+000 1.53124332e-001 9.60791826e-001 -2.17997265e+000 + 4.00742739e-001 -4.14159775e-001 + <_> + -1.63437963e+000 -1.39622763e-001 9.27711725e-001 -4.78385627e-001 + -2.83107664e-002 -8.35023820e-002 + <_> + -1.37549627e+000 -6.85578808e-002 1.21474910e+000 6.36969984e-001 + 4.47012395e-001 -2.58403748e-001 + <_> + -1.19587171e+000 -1.10770442e-001 9.04386342e-001 6.66261077e-001 + 1.98683530e-001 -1.02757663e-002 + <_> + -7.25918591e-001 -9.43890437e-002 7.63390005e-001 1.62385452e+000 + 1.81978554e-001 -2.82774549e-002 + <_> + -1.61714935e+000 3.66413109e-002 1.20873821e+000 6.00123227e-001 + 6.92315757e-001 -4.73761946e-001 + <_> + -9.26650167e-001 -1.82191789e-001 1.05817747e+000 8.92111957e-001 + 2.05889136e-001 1.21550366e-001 + <_> + -1.86311018e+000 3.88235897e-001 -1.77206719e+000 1.65762019e+000 + 8.87961566e-001 -6.86816454e-001 + <_> + -1.79160488e+000 4.18196350e-001 -5.72709620e-001 3.37798506e-001 + 8.58134687e-001 -2.82030731e-001 + <_> + -1.22516298e+000 -9.58262861e-001 8.20484817e-001 1.33191526e+000 + 6.31532967e-001 -2.82074243e-001 + <_> + -1.54392111e+000 6.56023109e-003 1.98213249e-001 1.18591595e+000 + 1.01607621e+000 -7.90805459e-001 + <_> + 3.25552732e-001 3.65180194e-001 -3.70410234e-001 -1.78740948e-001 + -6.13813877e-001 2.03273892e-001 + <_> + -6.38491958e-002 -2.68768799e-003 -2.70019114e-001 + -2.21905559e-001 -2.34226301e-001 5.53561151e-001 + <_> + 2.03218505e-001 4.04533356e-001 -2.26719916e-001 -4.94212300e-001 + -5.95876575e-001 7.20375180e-002 + <_> + -4.81250137e-001 -1.18302450e-001 7.63773024e-002 -7.63308048e-001 + 2.36210283e-002 5.11956930e-001 + <_> + 4.90084440e-002 3.52215379e-001 -7.97088385e-001 1.18997522e-001 + -1.06313825e+000 1.03421891e+000 + <_> + -2.00398326e-001 1.53369144e-001 -8.62184286e-001 -2.56042659e-001 + -1.04408813e+000 7.87329733e-001 + <_> + -2.66303718e-001 5.33876792e-002 -5.83026826e-001 -4.89236534e-001 + 1.78114623e-001 -2.52873421e-001 + <_> + -3.79161358e-001 5.24220057e-003 -1.29849887e+000 2.09173635e-002 + -4.35289919e-001 6.95221841e-001 + <_> + -2.70611256e-001 9.70133692e-002 -7.17620015e-001 1.05627447e-001 + 6.95599437e-001 -4.36188728e-001 + <_> + -1.04340106e-001 1.05933458e-001 -4.70928282e-001 -1.23064391e-001 + -9.42853928e-001 9.50510025e-001 + <_> + 5.80542833e-002 -7.03638434e-001 1.03340137e+000 8.03414464e-001 + 1.78636408e+000 -1.95045698e+000 + <_> + 1.36927044e+000 -1.83150065e+000 2.92610794e-001 -2.08539888e-001 + 1.01815164e+000 -1.35973704e+000 + <_> + 3.97919476e-001 -5.62576532e-001 -1.36643067e-001 -4.05570388e-001 + 1.14966846e+000 -8.32581937e-001 + <_> + 1.41794574e+000 -1.97406721e+000 -1.97306313e-002 -8.67885530e-001 + 3.25625360e-001 -1.70996583e+000 + <_> + 2.48447716e-001 -7.61340678e-001 -2.04923227e-001 -7.59107590e-001 + 1.54269576e+000 -1.46080017e+000 + <_> + 1.33954847e+000 -2.34706450e+000 -1.99080145e+000 -1.23325789e+000 + -1.64900994e+000 7.93360353e-001 + <_> + 1.37443948e+000 -2.04091406e+000 -1.81673932e+000 -9.11762595e-001 + -1.81192696e+000 1.09470809e+000 + <_> + 1.49677372e+000 -4.58782166e-001 1.09795785e+000 -4.25706714e-001 + -3.79608311e-002 -1.34455800e-001 + <_> + 1.36582446e+000 9.89660859e-001 1.76499654e-002 -6.64653182e-001 + -8.27321470e-001 5.66557229e-001 + <_> + 9.35070097e-001 -1.22004676e+000 -2.10150099e+000 1.53519198e-001 + 1.76438272e+000 -2.41075015e+000 + <_> + 1.57258654e+000 -1.42949450e+000 -6.75881267e-001 3.41120847e-002 + -1.83193552e+000 -3.00656819e+000 + <_> + 9.92791176e-001 -1.03255928e+000 -1.51939344e+000 4.12432790e-001 + 7.31476665e-001 -1.61496830e+000 + <_> + 1.33265638e+000 -1.35544586e+000 -2.05558014e+000 2.21030023e-002 + -4.35853571e-001 -2.58475995e+000 + <_> + 1.31585693e+000 -9.24543679e-001 -6.64840102e-001 -7.57893249e-002 + 1.94185400e+000 -1.19324040e+000 + <_> + 1.56440222e+000 -1.12880659e+000 -1.29413211e+000 -2.25621551e-001 + 1.00537670e+000 -1.02480650e+000 + <_> + 1.43991423e+000 -9.99151766e-001 -6.63123965e-001 1.37939394e-001 + 1.06644082e+000 -4.46693331e-001 + <_> + 1.03931260e+000 -9.31187987e-001 -1.03366780e+000 7.81708181e-001 + 1.46791768e+000 -4.40328956e-001 + <_> + 1.58249390e+000 1.14829384e-001 9.20131207e-002 2.77604838e-003 + 7.34375000e-001 1.07801303e-001 + <_> + 1.15346253e+000 -9.43157852e-001 -1.98702300e+000 7.37556815e-002 + 1.70727837e+000 -1.85851920e+000 + <_> + 1.07506526e+000 -9.33865905e-001 -2.44212604e+000 4.98325318e-001 + 9.78095651e-001 -4.03932422e-001 + <_> + -2.20213026e-001 -3.47366095e-001 5.77793047e-002 1.57176065e+000 + -1.13385963e+000 2.36796427e+000 + <_> + 1.57818627e+000 -1.81356835e+000 -1.21137038e-001 5.16014278e-001 + -2.27868795e+000 1.36137640e+000 + <_> + 1.53597236e+000 -1.86727691e+000 -8.57961655e-001 1.48995304e+000 + -3.30473709e+000 2.26170564e+000 + <_> + -5.26660541e-003 -2.18390644e-001 4.70921427e-001 2.51552081e+000 + -2.04104519e+000 2.13210535e+000 + <_> + -2.03430075e-002 -2.47010261e-001 8.67236495e-001 1.38070190e+000 + -1.20167780e+000 9.94301915e-001 + <_> + -9.46464896e-001 -6.49423599e-002 1.36324763e+000 2.35076356e+000 + -2.18438697e+000 1.80466747e+000 + <_> + 2.60078073e-001 -3.29107672e-001 -1.39126968e+000 4.50684404e+000 + -1.30533469e+000 1.38534248e+000 + <_> + 1.35376334e+000 -1.22824991e+000 -1.25749564e+000 2.03819013e+000 + -2.52228951e+000 2.20885754e+000 + <_> + -1.32339135e-001 1.09741427e-001 -2.46852368e-001 -7.20155060e-001 + 3.41148302e-002 3.34276944e-001 + <_> + -1.25877827e-001 3.05495020e-002 -7.76843488e-001 -2.87393332e-001 + 7.49736547e-001 -4.77527946e-001 + <_> + 1.35764015e+000 -4.04454470e-001 7.43843019e-001 -5.43132484e-001 + -7.78946519e-001 5.69124281e-001 + <_> + -4.13190961e-001 1.70290638e-002 -4.21612412e-001 2.56557703e-001 + 1.15155602e+000 -5.52049756e-001 + <_> + -2.04705864e-001 1.15036152e-001 4.12569046e-001 1.63059008e+000 + 3.28517705e-001 -1.10323718e-002 + <_> + -1.01755582e-001 -1.06691703e-001 1.61472961e-001 4.16625798e-001 + -1.38055801e+000 8.54309320e-001 + <_> + 8.43750238e-001 -1.39753222e-001 1.46225429e+000 -2.77426183e-001 + -5.97787425e-002 9.89183411e-002 + <_> + -6.60029650e-002 2.48510931e-002 -1.63381100e+000 -3.12062204e-001 + -3.44173938e-001 3.74948442e-001 + <_> + 8.91563892e-001 -1.43477723e-001 1.53237748e+000 -7.01384339e-003 + -5.47708511e-001 4.21505541e-001 + <_> + 1.49074316e+000 -1.15416420e+000 -8.14953804e-001 5.65098882e-001 + -7.16110587e-001 1.09528100e+000 + <_> + 1.18189228e+000 -2.24753749e-002 1.24920797e+000 1.75796664e+000 + -5.05650043e-001 8.09420824e-001 + <_> + 1.09014165e+000 -8.33601058e-002 1.54444540e+000 6.40765667e-001 + -5.39487481e-001 1.14083278e+000 + <_> + 8.56242120e-001 -1.59716952e+000 2.55633980e-001 -2.46546960e+000 + -2.46148512e-001 -4.14065778e-001 + <_> + 3.65182132e-001 -7.07461417e-001 2.90372580e-001 -1.69787693e+000 + 1.33725271e-001 -3.64088386e-001 + <_> + 1.04060495e+000 -8.86205375e-001 7.87280127e-002 -1.26194012e+000 + -4.25413609e-001 6.82549477e-001 + <_> + 1.02552855e+000 -1.96168900e+000 -6.32438017e-003 -1.06881011e+000 + -1.72121739e+000 1.01592088e+000 + <_> + 8.15751195e-001 -3.44584793e-001 1.59814492e-001 3.82120681e+000 + 5.43654382e-001 -1.24317400e-001 + <_> + 1.45671356e+000 -3.97507638e-001 6.77766502e-001 2.86954498e+000 + 2.60730088e-001 -6.91323578e-002 + <_> + 1.46015966e+000 -4.70424920e-001 -1.63940132e-001 2.54198623e+000 + -1.47995758e+000 1.22948313e+000 + <_> + 1.60317004e+000 -2.09037924e+000 -3.53874505e-001 2.51221871e+000 + -1.90037918e+000 1.36712611e+000 + <_> + 1.61738503e+000 -2.00875592e+000 -6.02267981e-001 3.02059436e+000 + -1.34138238e+000 1.14388263e+000 + <_> + 1.59757030e+000 -6.42246842e-001 5.33262789e-001 8.24849188e-001 + -8.27826023e-001 8.90068412e-001 + <_> + 1.61652350e+000 1.36256886e+000 6.98305607e-001 2.44959995e-001 + -1.61039758e+000 1.23634422e+000 + <_> + 1.52821887e+000 -1.72542274e+000 1.17856836e+000 3.14579725e+000 + -6.33606911e-001 7.84065902e-001 + <_> + 1.47695911e+000 -2.85550445e-001 2.03317952e+000 4.65580255e-001 + -1.19430149e+000 7.07579076e-001 + <_> + 6.76617563e-001 -1.61178041e+000 -1.02251422e+000 3.58931422e+000 + -7.26189852e-001 8.88974369e-001 + <_> + -1.38800457e-001 -8.08654726e-002 -1.29609048e+000 4.13288832e-001 + -1.26718247e+000 1.30440748e+000 + <_> + 5.62036872e-001 -6.21854626e-002 -1.10431034e-002 1.92475945e-001 + 2.86426842e-001 -1.89648941e-001 + <_> + 1.51400399e+000 1.52353868e-001 -5.35957813e-001 -9.30676341e-001 + 1.20711577e+000 -2.61284208e+000 + <_> + 7.70091236e-001 -1.57175392e-001 3.87775570e-001 2.38873258e-001 + 1.17554128e+000 -7.84053802e-001 + <_> + 1.30724180e+000 -2.51486868e-001 -1.51220572e+000 1.26544738e+000 + 2.30253363e+000 -1.91769922e+000 + <_> + 1.57689404e+000 -3.19323599e-001 1.38989282e+000 4.60548848e-001 + 1.62663329e+000 -1.74798226e+000 + <_> + 1.47782052e+000 -2.70507455e-001 -6.11843884e-001 1.24676585e+000 + 2.36043310e+000 -2.16975021e+000 + <_> + 1.57646334e+000 -1.22834945e+000 -1.04359269e+000 1.40797186e+000 + 1.30367625e+000 -1.23211801e+000 + <_> + 1.41234589e+000 -3.58092934e-001 -7.20824480e-001 1.21742630e+000 + -2.39091501e-001 2.38665611e-001 + <_> + 1.60144711e+000 -9.16842043e-001 -1.26290953e+000 1.35964918e+000 + -9.30639803e-001 9.92132008e-001 + <_> + 9.26885784e-001 -1.88156092e+000 1.01104796e+000 5.09437360e-002 + 1.58790970e+000 -1.21337807e+000 + <_> + 3.56136292e-001 -1.19817150e+000 1.35851443e+000 9.54661429e-001 + 5.70210040e-001 4.81014811e-002 + <_> + 9.09224808e-001 -9.51381803e-001 2.09281826e+000 2.62239844e-001 + 8.91116560e-002 -2.02228022e+000 + <_> + 4.08688337e-001 -4.86549884e-001 2.41952348e+000 3.02302092e-001 + 1.71841949e-001 -2.91668683e-001 + <_> + 8.12735915e-001 -6.12091959e-001 2.45539856e+000 9.07018960e-001 + 7.62409031e-001 -6.12391710e-001 + <_> + 7.02032030e-001 -1.44144142e+000 2.21728754e+000 5.57762623e-001 + 5.12658775e-001 -7.02850580e-001 + <_> + 8.03259313e-001 -1.99679852e+000 1.08046746e+000 5.35640121e-001 + -2.39182770e-001 -6.03285730e-001 + <_> + 5.55575550e-001 -1.48068234e-001 2.72720084e-002 -1.11282885e-001 + 5.94903946e-001 -1.78575718e+000 + <_> + 1.40459228e+000 1.99870145e+000 1.65283191e+000 -6.37054026e-001 + 3.84290159e-001 -4.29816218e-003 + <_> + 1.60747766e+000 2.35400128e+000 -3.74332905e-001 -9.08323348e-001 + -1.70479739e+000 1.86106706e+000 + <_> + 1.38736212e+000 1.54740274e+000 7.32305825e-001 -7.04355359e-001 + -1.28118050e+000 1.19551325e+000 + <_> + 1.38650060e+000 2.30237889e+000 -2.45554432e-001 -2.00361109e+000 + -5.18275797e-001 -2.32201576e+000 + <_> + 1.42182255e+000 7.30606019e-001 8.90809953e-001 -1.26940131e+000 + -4.55255121e-001 -1.71369469e+000 + <_> + 1.13924766e+000 1.12454998e+000 2.93288589e-001 -7.59783328e-001 + -1.69460207e-001 -3.89328152e-002 + <_> + 1.07118845e+000 3.88128698e-001 7.59709597e-001 5.35329103e-001 + -5.25014937e-001 5.71410120e-001 + <_> + 1.27364302e+000 2.20538497e+000 -5.12474366e-002 -1.10152829e+000 + 3.86274397e-001 -3.71776223e-001 + <_> + 1.28182733e+000 1.08241796e+000 6.75288856e-001 -5.13061404e-001 + -1.76366496e+000 1.85776055e+000 + <_> + 1.42569923e+000 -1.28460228e-001 -4.07767773e-001 -2.76201844e-001 + -2.36741185e+000 2.37593865e+000 + <_> + 1.39683878e+000 1.92648590e+000 1.36546791e-001 1.69534922e-001 + -1.24411952e+000 1.36502028e+000 + <_> + 1.57990932e+000 1.33854282e+000 5.19605637e-001 -3.60225379e-001 + -2.50161791e+000 2.29605293e+000 + <_> + 1.44594479e+000 2.55657464e-001 -8.09459209e-001 -1.87868130e+000 + -1.22333467e+000 -5.88352323e-001 + <_> + 1.38133156e+000 6.16234779e-001 -1.53141290e-001 -1.77324033e+000 + -1.20574963e+000 3.32485661e-002 + <_> + 1.53941846e+000 7.60800362e-001 -5.90828836e-001 -1.50307775e+000 + -1.99901783e+000 3.65938962e-001 + <_> + 1.45757508e+000 2.64476180e-001 -1.23969421e-001 -9.06286359e-001 + -1.47641683e+000 -2.94484466e-001 + <_> + 1.34170222e+000 3.32680345e-001 -1.17359698e+000 -1.51489604e+000 + -5.60501158e-001 -1.02714434e-001 + <_> + 1.59584725e+000 1.58842933e+000 -6.44073129e-001 -1.37418056e+000 + -1.49853158e+000 1.05366921e+000 + <_> + 7.94644237e-001 1.13311088e+000 -8.21134865e-001 -1.24210811e+000 + -3.33229780e-001 -1.29839554e-002 + <_> + 1.53338790e+000 1.20463356e-001 -4.42321748e-001 -1.20923829e+000 + -2.00439715e+000 1.18496764e+000 + <_> + 1.35634780e+000 -3.16397309e-001 -3.61180454e-001 -1.01775026e+000 + -1.81086302e+000 8.31826925e-001 + <_> + 1.32964110e+000 -5.05683839e-001 -1.02014613e+000 -7.14940429e-001 + -2.33705091e+000 1.37994885e+000 + <_> + 5.03007062e-002 -1.97293207e-001 -2.69156694e-001 -7.91413426e-001 + 1.25479889e+000 -2.28063130e+000 + <_> + 5.08623362e-001 -2.04642847e-001 -2.17940092e-001 -6.12555027e-001 + 1.49880898e+000 -2.52386689e+000 + <_> + -2.03413606e-001 -6.42685816e-002 -1.50826001e+000 + -5.19323945e-001 1.41635716e+000 -2.31420374e+000 + <_> + -1.12524435e-001 -1.30845889e-001 -7.93070555e-001 + -3.31635475e-001 2.56913960e-001 -2.02591252e+000 + <_> + -1.47415534e-001 -2.31629580e-001 -1.89269459e+000 + -4.35933173e-001 3.98963392e-001 -1.88445354e+000 + <_> + -2.07290396e-001 -2.20684767e-001 -2.05324078e+000 + -5.63241065e-001 1.10968649e+000 -2.29912877e+000 + <_> + 7.44676709e-001 -5.81535995e-001 -2.12596583e+000 -1.59655660e-001 + -6.77217007e-001 -1.23245247e-001 + <_> + 9.12240088e-001 -1.93295610e+000 -3.13378870e-001 1.21018052e+000 + 1.15714180e+000 -8.32110167e-001 + <_> + 5.43514431e-001 -1.73889780e+000 -3.69752616e-001 -1.89141080e-001 + 5.85986748e-002 -6.58662856e-001 + <_> + 6.06835306e-001 -4.59693372e-001 2.38328159e-001 -2.90145069e-001 + -1.37414587e+000 -8.36838409e-002 + <_> + 1.60187781e+000 -1.55739748e+000 -1.48333147e-001 3.64431113e-001 + -1.44993114e+000 6.34229124e-001 + <_> + 1.38822365e+000 -8.33678365e-001 -1.75300777e+000 -3.35325189e-002 + -3.20226014e-001 3.56422246e-001 + <_> + 9.21285927e-001 -1.87788829e-001 -2.15696049e+000 -1.93281904e-001 + -1.17727566e+000 1.01384032e+000 + <_> + 9.69099700e-001 -1.40807045e+000 -2.09194255e+000 -4.74496037e-001 + -1.48938477e-001 3.75245780e-001 + <_> + 1.46705174e+000 -2.38819695e+000 -2.76710939e+000 -8.78998220e-001 + -1.16025174e+000 7.23468065e-001 + <_> + 8.72179985e-001 -1.45272744e+000 -1.97009504e+000 -7.90637970e-001 + 1.07935023e+000 -1.23002434e+000 + <_> + 9.66945887e-001 -1.43746161e+000 -2.06879592e+000 -6.92175031e-001 + 1.19572234e+000 -1.47336268e+000 + <_> + 1.43905270e+000 -2.37532759e+000 -2.49976015e+000 -1.18248868e+000 + -1.01939273e+000 7.70002544e-001 + <_> + 1.55751014e+000 -2.16052270e+000 -7.18528569e-001 -3.24027389e-001 + -1.67584315e-001 -3.24121624e-001 + <_> + 1.52563429e+000 -2.32151175e+000 -1.05080056e+000 -1.49673700e+000 + 2.36097410e-001 -1.17021024e+000 + <_> + 1.37443948e+000 -6.67220950e-001 -1.83373082e+000 -1.03760791e+000 + -2.22159529e+000 -3.34707499e+000 + <_> + 1.47049773e+000 -9.02642012e-001 -3.50869060e-001 -8.74411225e-001 + 1.43795562e+000 -1.62237000e+000 + <_> + 1.44249868e+000 1.10697496e+000 -1.48032844e+000 -9.43161309e-001 + -4.21454869e-002 2.86980569e-001 + <_> + 1.50754261e+000 1.95500588e+000 -1.95676661e+000 -6.88107133e-001 + -8.36062551e-001 8.05729449e-001 + <_> + 1.23875189e+000 2.18399978e+000 -1.52800882e+000 -6.37401998e-001 + -7.01636016e-001 7.89005280e-001 + <_> + 1.27924287e+000 -6.02189377e-002 -1.40527010e+000 1.82350606e-001 + 4.60502245e-002 1.57384798e-001 + <_> + 1.38779294e+000 -1.36958957e-001 -1.37004983e+000 -1.22778982e-001 + -8.89026284e-001 4.14883465e-001 + <_> + 3.13475132e-002 -3.71057957e-001 -1.66153491e+000 1.37733245e+000 + 8.07662964e-001 -1.41810775e-002 + <_> + 7.26154268e-001 -1.65630615e+000 -1.11088645e+000 -5.10214157e-002 + 4.70126182e-001 -8.24912786e-002 + <_> + 1.36453223e+000 -1.21932220e+000 2.01103377e+000 -9.17819321e-001 + -5.72320819e-001 -2.89959162e-001 + <_> + 6.70156240e-001 -1.46180618e+000 -5.56540251e-001 1.00167483e-001 + -1.47398159e-001 -1.74863684e+000 + <_> + 1.35764015e+000 -9.31053758e-001 1.44684923e+000 -2.53403991e-001 + -1.08080256e+000 -1.63683474e-001 + <_> + 4.16011155e-001 -1.83661711e+000 -1.89404738e+000 -8.44894797e-002 + -1.56369126e+000 8.38237464e-001 + <_> + 1.58206308e+000 4.13128525e-001 2.21608186e+000 -1.63839996e+000 + -6.01611078e-001 -3.92180175e-001 + <_> + 6.94278479e-001 -1.26854289e+000 1.95271254e-001 -5.07989347e-001 + 1.56440639e+000 -1.84689569e+000 + <_> + 1.57129431e+000 -5.15492618e-001 1.57465565e+000 -1.51630533e+000 + -7.99717426e-001 -5.32666683e-001 + <_> + -1.50000066e-001 -6.45596564e-001 -2.86044300e-001 + -1.97933137e+000 -1.02929831e+000 -1.17085993e+000 + <_> + 1.16810822e+000 -1.37866363e-001 -2.80298769e-001 -1.55238736e+000 + -1.88074780e+000 -1.19976294e+000 + <_> + 8.30396831e-001 -1.95349500e-001 -8.30713630e-001 -2.10786700e+000 + 7.02910796e-002 -1.67845261e+000 + <_> + 1.17672324e+000 -7.73396075e-001 -1.63845468e+000 -1.41897988e+000 + -2.46166900e-001 -1.10678363e+000 + <_> + 1.42957604e+000 7.31905282e-001 1.64843035e+000 -1.40254462e+000 + -1.16903651e+000 -1.57216465e+000 + <_> + 1.26718163e+000 6.38384163e-001 -1.91589022e+000 -8.62150490e-001 + -1.58648026e+000 -2.15923473e-001 + <_> + 1.15389335e+000 2.86782891e-001 -2.37022305e+000 -1.17652237e+000 + -2.00657845e+000 5.07111549e-001 + <_> + 1.44766772e+000 1.18743861e+000 -2.67927974e-001 -2.11026287e+000 + 3.20772409e-001 -1.82418704e+000 + <_> + 1.54975653e+000 1.08881116e+000 2.65558898e-001 -7.82213449e-001 + -2.64190030e+000 5.77981114e-001 + <_> + 1.56483293e+000 1.68797779e+000 -3.11351210e-001 -1.44286203e+000 + -6.24800801e-001 2.88837075e-001 + <_> + 1.48643565e+000 1.14601958e+000 -6.06404066e-001 -8.08813751e-001 + -2.75393510e+000 3.88581723e-001 + <_> + -1.43538743e-001 -2.37000920e-002 3.55632871e-001 7.38045037e-001 + -9.48605001e-001 7.12978542e-001 + <_> + 4.24195498e-001 -3.77409339e-001 1.08460069e+000 -4.04774398e-002 + 1.91777992e+000 -1.35957658e+000 + <_> + 1.49289691e+000 -1.22954130e+000 -8.89158309e-001 -3.72362018e-001 + -2.09647965e+000 2.88257837e-001 + <_> + 1.58421683e+000 3.06841278e+000 1.58413064e+000 -2.75038743e+000 + -6.87910855e-001 7.17725575e-001 + <_> + 2.67400920e-001 -5.46021223e-001 -6.70516431e-001 -1.56816709e+000 + -5.91437936e-001 2.20211059e-001 + <_> + 1.61264670e+000 -1.23780596e+000 -1.26778737e-001 -8.12235653e-001 + -2.13336325e+000 1.90407979e+000 + <_> + 1.38090086e+000 -1.21298218e+000 -2.47154593e+000 6.91761434e-001 + -8.39515388e-001 1.02140880e+000 + <_> + 1.57387877e+000 -2.85904646e+000 -1.94242013e+000 -7.10644245e-001 + -1.90660465e+000 5.06658077e-001 + <_> + 8.11012864e-001 -1.90052903e+000 -8.83141637e-001 -6.11994743e-001 + -9.68516529e-001 -5.82059205e-001 + <_> + 8.34273636e-001 -1.87418199e+000 -5.85008323e-001 -4.12384748e-001 + -9.73380923e-001 -5.00069320e-001 + <_> + 1.18060005e+000 -2.74265146e+000 -5.58288097e-001 -9.20644820e-001 + -9.60822821e-001 3.92176025e-003 + <_> + 1.09487998e+000 -2.78837609e+000 -1.16780531e+000 -2.47411156e+000 + 3.17497142e-002 -2.57052445e+000 + <_> + 1.07161927e+000 -2.50926089e+000 6.01231456e-001 -2.76662540e+000 + 3.97291511e-001 -2.19512153e+000 + <_> + 1.60532391e+000 -2.84321547e+000 -1.66444238e-002 -2.37789583e+000 + -3.57171208e-001 -1.24852479e+000 + <_> + 9.79437768e-001 -2.51027036e+000 -1.16377878e+000 -2.28793120e+000 + 5.91421366e-001 -1.77829421e+000 + <_> + 1.08626485e+000 -1.50608242e+000 1.38596439e+000 -8.05097342e-001 + -1.73142806e-001 -7.91484118e-001 + <_> + 1.55404761e-001 -1.19310550e-001 1.76375651e+000 -2.50990361e-001 + 5.60226142e-001 -6.67708516e-001 + <_> + -3.73561561e-001 -9.52011198e-002 -4.26054239e-001 2.55777955e-001 + -1.45410895e-001 -2.29326069e-001 + <_> + -1.20278016e-001 4.51590531e-002 9.52666759e-001 8.45297992e-001 + 5.76352835e-001 -7.90016592e-001 + <_> + -2.46505514e-002 -1.05971374e-001 1.21819592e+000 3.31079245e-001 + 6.82462394e-001 -7.37480581e-001 + <_> + 1.38477767e+000 -1.28486240e+000 -1.58594882e+000 -9.86078739e-001 + 7.83477962e-001 -1.43785310e+000 + <_> + -5.09265661e-002 -3.40973318e-001 2.57399201e-001 8.82323980e-002 + 1.34484053e+000 -1.46723104e+000 + <_> + -1.12955190e-001 -2.36109093e-001 -3.91018599e-001 + -2.92542577e-001 9.44516420e-001 -1.15791988e+000 + <_> + 1.00657535e+000 -1.09063566e+000 -2.51559556e-001 1.58787906e+000 + 4.31479394e-001 2.67520308e-001 + <_> + 1.14140141e+000 -1.44726181e+000 3.18471462e-001 5.37345111e-001 + -1.24777985e+000 1.50413024e+000 + <_> + 1.21850646e+000 -6.08221531e-001 -5.68665862e-001 1.94427609e+000 + 6.45777225e-001 -1.40130055e+000 + <_> + 1.17241573e+000 -3.02517921e-001 -5.39528608e-001 2.54557824e+000 + -2.28339266e-002 -3.72154593e-001 + <_> + 1.33653319e+000 -5.79496324e-001 3.23230565e-001 1.66887009e+000 + 6.32619560e-001 -8.82869899e-001 + <_> + 1.36711669e+000 -8.23292792e-001 -9.29215550e-001 2.98728991e+000 + -9.46097910e-001 7.18037248e-001 + <_> + 1.29862678e+000 -6.08849227e-001 -1.48887455e+000 1.11523640e+000 + -9.73840177e-001 4.37011629e-001 + <_> + 8.07997584e-001 -1.35097551e+000 -1.15596235e+000 -3.06712896e-001 + -1.14499760e+000 -8.33389640e-001 + <_> + 1.33222556e+000 -2.58078909e+000 -9.80688870e-001 -7.98956513e-001 + -4.54008840e-002 -1.65321589e+000 + <_> + 1.45843661e+000 -1.92801917e+000 -1.20958751e-002 -1.91213095e+000 + -6.06637150e-002 -1.07648492e+000 + <_> + 1.27321219e+000 -1.87864184e+000 -1.22305703e+000 -9.56209838e-001 + -9.50783670e-001 -4.04371738e-001 + <_> + 1.43732965e+000 -2.21820974e+000 -1.90486932e+000 -2.20576346e-001 + -1.78280663e+000 -9.83308181e-002 + <_> + 1.53123415e+000 -2.63162732e+000 -1.23447597e+000 -1.79970706e+000 + -2.49971732e-001 -1.65424836e+000 + <_> + 1.53468013e+000 -2.67711186e+000 -1.65242910e+000 -1.14393508e+000 + -1.13848770e+000 -9.06858623e-001 + <_> + 1.54114151e+000 -2.68072844e+000 -1.81460214e+000 -1.05408454e+000 + -9.47292507e-001 -1.24822772e+000 + <_> + 7.58030117e-001 -1.82013953e+000 -9.79800463e-001 -1.00784428e-001 + -2.45742679e+000 1.83910966e+000 + <_> + 8.04982305e-001 -2.92971253e-001 4.75363255e-001 -1.65584886e+000 + -2.20344022e-001 -2.48983994e-001 + <_> + 1.85126811e-001 -7.26308107e-001 -2.43365765e+000 2.46122217e+000 + -1.28008556e+000 1.16300392e+000 + <_> + 1.58981669e+000 -2.45136356e+000 -1.10786080e+000 1.18697882e+000 + -1.32429957e+000 1.08066905e+000 + <_> + 1.58550918e+000 -1.22847450e+000 -2.31362724e+000 7.41963685e-002 + -1.64935815e+000 1.06004047e+000 + <_> + 2.59216577e-001 -7.65895918e-002 -9.97103453e-001 -7.87760556e-001 + -1.65699053e+000 9.21730876e-001 + <_> + 1.61178517e+000 -2.34088326e+000 -8.82111967e-001 -4.54132497e-001 + -1.61509788e+000 8.58645856e-001 + <_> + 9.52300251e-001 -1.60871959e+000 1.51726902e-001 7.31931388e-001 + 1.44278097e+000 -1.51117802e+000 + <_> + 6.54218316e-001 -2.88868308e+000 -3.46432686e+000 -6.55023813e-001 + -1.34307468e+000 -1.29441822e+000 + <_> + -1.54307604e-001 -1.06876576e+000 2.46756107e-001 2.12551266e-001 + 1.99073601e+000 -1.65691888e+000 + <_> + 5.42222202e-001 -1.75004959e+000 1.75036752e+000 1.42800999e+000 + -8.89732987e-002 -1.28037000e+000 + <_> + 1.29474998e+000 -2.74804425e+000 1.77364945e-002 -8.08538377e-001 + -4.44020718e-001 -1.88301718e+000 + <_> + 1.41062284e+000 -2.75734568e+000 -1.04761338e+000 -3.26911569e-001 + -6.42621219e-001 -1.01296258e+000 + <_> + 1.10694110e+000 -2.64306808e+000 3.84553790e-001 1.33336335e-001 + -5.17407842e-002 -5.68526387e-001 + <_> + 2.76877493e-001 -1.74007380e+000 2.05594182e-001 4.11607653e-001 + 1.43160373e-001 -2.15391612e+000 + <_> + 5.69790483e-001 -1.51799285e+000 1.68134320e+000 6.84410810e-001 + 1.54332113e+000 -1.77419317e+000 + <_> + -2.74918824e-001 -9.07268345e-001 5.77228248e-001 2.28681296e-001 + 6.12347186e-001 -6.64828241e-001 + <_> + 9.12240088e-001 -2.36018133e+000 -9.69483316e-001 3.25357288e-001 + -1.48918703e-001 -3.20208259e-002 + <_> + 2.62662619e-001 -1.27920866e+000 -2.67873198e-001 3.69898796e-001 + 5.21866918e-001 -1.10632014e+000 + <_> + 1.15389335e+000 -7.51695991e-001 -9.90890622e-001 4.26763833e-001 + -2.84010744e+000 2.22402143e+000 + <_> + 1.03715885e+000 -8.19252074e-001 1.06549788e+000 -2.61898220e-001 + -1.76163268e+000 1.51834452e+000 + <_> + 2.30356038e-001 -4.44923609e-001 5.96235812e-001 -1.29034197e+000 + -2.06456244e-001 1.40681460e-001 + <_> + -5.56648634e-002 -2.56403178e-001 -7.43206680e-001 + -9.86996651e-001 -3.93747449e-001 1.99904293e-002 + <_> + 2.29063779e-001 -2.84347683e-001 7.61881471e-001 -3.12674046e-001 + -1.72207981e-001 2.78432995e-001 + <_> + 2.98415214e-001 -2.38027290e-001 8.34358335e-001 -7.76262641e-001 + -7.99524844e-001 5.76834321e-001 + <_> + 4.02227014e-001 -3.29312861e-001 5.07647276e-001 -3.84416133e-001 + -1.21922886e+000 8.12345207e-001 + <_> + 2.44140178e-001 -3.32440794e-001 9.09517527e-001 -8.14108849e-001 + -1.01282573e+000 6.17700040e-001 + <_> + 1.60230863e+000 -1.20130682e+000 -9.64585781e-001 -7.61006534e-001 + 8.13511670e-001 -1.07071888e+000 + <_> + -1.78860605e-001 -4.88135308e-001 2.13148177e-001 1.64440334e+000 + -1.08888495e+000 9.45559919e-001 + <_> + 1.16810822e+000 -6.61123812e-001 -1.19488311e+000 2.04092741e+000 + -2.98422992e-001 8.47688556e-001 + <_> + -2.12890193e-001 -3.77121806e-001 -6.25186682e-001 1.19580507e+000 + -5.82442045e-001 5.22699654e-001 + <_> + -1.94798514e-001 -3.76115263e-001 -2.38210991e-001 3.00052428e+000 + -3.61440271e-001 3.21445674e-001 + <_> + 1.01562119e+000 -6.25912011e-001 -3.50372970e-001 8.37347746e-001 + -7.29919523e-002 9.52994406e-001 + <_> + -3.58054399e-001 -5.75871281e-002 8.34234297e-001 4.72888261e-001 + 8.01081836e-001 -1.41756654e+000 + <_> + -4.21806037e-001 -7.63273239e-002 6.69820249e-001 5.13767064e-001 + 1.08758414e+000 -1.63986492e+000 + <_> + 1.57689404e+000 -2.46164227e+000 1.99146950e+000 1.43008852e+000 + -1.68716125e-002 -2.15869248e-001 + <_> + 1.32102597e+000 -1.46537590e+000 2.00015998e+000 2.06711316e+000 + -3.54960263e-001 8.83992538e-002 + <_> + 1.40976143e+000 -1.45106769e+000 2.79910374e+000 8.40828240e-001 + -1.04509437e+000 3.98714364e-001 + <_> + 9.99252498e-001 -2.54985380e+000 8.24542999e-001 1.58837795e+000 + 8.01386684e-002 -1.37473002e-001 + <_> + 1.57732487e+000 -1.85262597e+000 2.08074689e-001 5.52801967e-001 + 1.65051508e+000 -1.69038415e+000 + <_> + 1.04965079e+000 -1.29584110e+000 3.76027793e-001 2.48041940e+000 + -1.53878236e+000 1.34593689e+000 + <_> + 8.66580188e-001 -1.22360301e+000 -6.89151943e-001 2.14729500e+000 + -5.50948739e-001 4.07157987e-001 + <_> + 1.58206308e+000 -1.88659525e+000 -4.07574505e-001 5.52480519e-001 + 1.78572702e+000 -1.66011012e+000 + <_> + 1.10091054e+000 -1.30526781e+000 3.80732089e-001 1.98418212e+000 + -5.84148705e-001 3.76296341e-001 + <_> + 1.55191028e+000 -1.45590532e+000 6.28000677e-001 2.37509534e-001 + -4.60315019e-001 -4.03896809e-001 + <_> + 1.53812623e+000 -1.59736979e+000 1.09186041e+000 1.10929704e+000 + 1.40566778e+000 -1.22548759e+000 + <_> + 6.36557400e-001 -1.74891102e+000 2.35175085e+000 3.26200753e-001 + 8.45651984e-001 -1.42521131e+000 + <_> + 8.94579172e-001 -1.23319256e+000 1.46588850e+000 2.12566185e+000 + -4.69572425e-001 2.59845912e-001 + <_> + 1.60618532e+000 -1.33014655e+000 1.57319561e-001 3.55645728e+000 + -1.76591015e+000 1.37718534e+000 + <_> + 1.58206308e+000 -1.56803942e+000 -7.22480059e-001 1.96045232e+000 + -2.37299919e+000 1.69764459e+000 + <_> + 1.61006212e+000 -1.75581002e+000 -1.08549930e-001 1.58743370e+000 + -1.12703741e+000 1.01414919e+000 + <_> + 1.14913858e-001 -5.21088541e-001 -1.99351263e+000 2.41728830e+000 + -6.48325205e-001 7.34801292e-001 + <_> + 9.23439741e-001 -3.11807901e-001 5.75644791e-001 1.65208304e+000 + -3.08647037e+000 2.67545033e+000 + <_> + 1.23056757e+000 -5.46175659e-001 4.22669888e-001 1.98055160e+000 + -2.52236986e+000 2.00322485e+000 + <_> + 1.25942814e+000 -6.25247657e-001 6.33662045e-002 6.05285072e+000 + -2.73559976e+000 2.11875367e+000 + <_> + 1.24995148e+000 -2.28714252e+000 -2.59912491e-001 -8.18009496e-001 + 1.13539851e+000 -7.56169617e-001 + <_> + 1.25038230e+000 -1.58881915e+000 3.35944034e-002 -1.12934291e+000 + -9.32637393e-001 3.97747010e-001 + <_> + 1.43086839e+000 -1.77600205e+000 5.61704934e-001 1.93675488e-001 + -5.93564391e-001 2.47957960e-001 + <_> + 1.32447207e+000 -8.86262894e-001 1.56135607e+000 -6.43248260e-002 + -9.05251324e-001 9.27491486e-001 + <_> + 1.15518558e+000 -2.15703678e+000 8.50585401e-001 -7.22319126e-001 + -8.73267591e-001 4.00679588e-001 + <_> + 1.46748245e+000 -4.51822340e-001 1.36720777e+000 7.70406723e-001 + 2.52690583e-001 -3.58742833e-001 + <_> + 1.32361054e+000 -3.25099885e-001 2.84901053e-001 6.26597047e-001 + 2.11281419e+000 -2.28566813e+000 + <_> + 1.18275380e+000 -1.02121818e+000 -1.71375811e+000 1.34567308e+000 + 1.48107380e-001 -1.18604444e-001 + <_> + 1.58507836e+000 -1.18102252e+000 -4.72358882e-001 -4.84374970e-001 + 5.90303957e-001 -8.87626827e-001 + <_> + 1.46403646e+000 -8.40034485e-001 1.17154515e+000 9.23253953e-001 + -1.78133595e+000 1.56728351e+000 + <_> + 1.57172501e+000 -8.91147435e-001 9.90485787e-001 5.53459406e-001 + -2.05215931e+000 1.70252192e+000 + <_> + 1.59627807e+000 -8.67526233e-001 6.91833258e-001 1.24838912e+000 + -1.81735289e+000 1.57074308e+000 + <_> + 1.26072037e+000 -9.57794070e-001 -1.34406519e+000 1.22917640e+000 + -2.03663015e+000 1.64942312e+000 + <_> + 1.54975653e+000 -1.44414532e+000 -1.30753255e+000 2.96498835e-001 + -1.00245905e+000 1.10324025e+000 + <_> + 1.53295708e+000 5.12864292e-001 1.05768716e+000 -1.48210299e+000 + 7.79478312e-001 -1.93269038e+000 + <_> + 1.02337468e+000 -1.09225547e+000 -1.04489364e-002 1.27284992e+000 + 1.59661484e+000 -1.52619195e+000 + <_> + 1.56009459e+000 -1.07489236e-001 1.51644742e+000 -1.21922874e+000 + -4.56269681e-001 -6.70687318e-001 + <_> + 1.49160469e+000 -1.42325675e+000 -1.00322974e+000 -8.51045370e-001 + -6.94962382e-001 3.03234667e-001 + <_> + -2.27105081e-001 -3.29766572e-002 -1.56151295e+000 + -9.05189335e-001 8.77947152e-001 -9.24464762e-001 + <_> + 9.55299139e-002 1.89169660e-001 -1.17014730e+000 3.14235538e-002 + 7.32676923e-001 -4.36049044e-001 + <_> + -1.64645717e-001 -4.58823889e-002 -2.17909169e+000 + -2.16308311e-001 -1.78873867e-001 5.46954632e-001 + <_> + -9.24927175e-001 7.31612742e-002 -6.81453705e-001 -2.14915518e-002 + 3.33138704e-001 -3.40551399e-002 + <_> + 6.90401673e-001 -1.02192275e-001 -5.10195196e-001 -3.47169667e-001 + -1.24594077e-001 -1.96637070e+000 + <_> + -2.03430075e-002 -6.66081384e-002 -2.34343076e+000 + -5.56401052e-002 3.28374393e-002 5.76280773e-001 + <_> + -1.21139526e-001 1.20061457e-001 -1.41981852e+000 -1.85379922e-001 + 3.09463948e-001 6.25562429e-001 + <_> + 7.44229481e-002 3.48712541e-002 -7.55023718e-001 -9.36868787e-002 + 1.45421013e-001 1.24965876e-001 + <_> + 3.65165658e-002 -9.80572402e-003 -7.48966634e-001 -1.85831413e-001 + 2.07619503e-001 1.34651542e-001 + <_> + 1.94172651e-001 -2.13557333e-001 5.88840485e-001 -2.17525855e-001 + 3.52804095e-001 -2.71051794e-001 + <_> + 2.29494527e-001 -3.33712041e-001 1.02338940e-001 -4.28488888e-002 + 5.19415498e-001 -3.89468402e-001 + <_> + 1.38606989e+000 -9.03985322e-001 4.55527343e-002 -1.21443772e+000 + 2.62229238e-002 -1.80979967e-001 + <_> + 2.74292976e-001 -5.55311918e-001 -3.03038627e-001 -9.79531527e-001 + 6.21953309e-001 -6.33375406e-001 + <_> + 7.32184827e-001 -5.68533838e-001 1.05304921e+000 -1.13580155e+000 + 1.77667999e+000 -2.49336076e+000 + <_> + 1.46446717e+000 -9.10208941e-001 5.64361393e-001 -7.13021040e-001 + -5.80217801e-002 -2.41195068e-001 + <_> + 5.85297644e-001 -4.82608318e-001 1.33379018e+000 -5.85569441e-001 + 1.61987197e+000 -1.17012680e+000 + <_> + 1.31973374e+000 -8.54184389e-001 8.93521190e-001 -7.54204631e-001 + 9.88541782e-001 -1.50264132e+000 + <_> + 8.42888713e-001 -6.38525069e-001 1.04423475e+000 -8.86660933e-001 + -1.37691164e+000 4.50558305e-001 + <_> + 1.42656076e+000 -1.41249442e+000 -1.78663588e+000 -2.76463836e-001 + -1.75764963e-001 2.48112500e-001 + <_> + 1.39554644e+000 -1.61367357e-001 -3.25726569e-001 -1.24715030e+000 + -3.21949840e-001 5.61215222e-001 + <_> + 1.57474029e+000 -1.72822371e-001 -1.57927454e+000 1.09793201e-001 + -1.47783923e+000 1.21996808e+000 + <_> + 1.34730196e+000 -1.54155120e-001 -2.33326364e+000 -4.21525538e-001 + -1.81348765e+000 1.52772081e+000 + <_> + 1.25038230e+000 5.30072749e-001 -2.50013232e+000 -7.57621527e-001 + -1.70059419e+000 1.57801366e+000 + <_> + 1.36065543e+000 2.30201587e-001 -3.11143517e-001 -1.08263862e+000 + -1.02777131e-001 -4.73187029e-001 + <_> + 1.47265148e+000 6.05809391e-001 -9.87213135e-001 -1.00023198e+000 + 2.44748801e-001 -1.00848019e+000 + <_> + 1.54415679e+000 1.55943537e+000 -8.90433192e-001 -2.51481295e+000 + -3.10677309e-002 -6.41372949e-002 + <_> + 1.39037740e+000 2.31857955e-001 -5.68596601e-001 -7.88343072e-001 + 5.73892057e-001 -6.51827037e-001 + <_> + 1.35333252e+000 -6.74550951e-001 -1.80417824e+000 1.19882607e+000 + -1.81419790e-001 2.92538375e-001 + <_> + 1.48686635e+000 -2.25149179e+000 -5.95512986e-001 -9.76230502e-002 + -1.52238816e-001 7.49306560e-001 + <_> + 9.37223852e-001 -2.22278500e+000 -7.63336241e-001 5.84261835e-001 + -1.37773156e+000 1.52379298e+000 + <_> + 1.35074806e+000 -2.09225488e+000 -6.67164862e-001 -5.16406715e-001 + -1.39334273e+000 1.54779530e+000 + <_> + 1.29216540e+000 -1.41576648e+000 1.23449169e-001 -9.88846958e-001 + -1.23886049e+000 1.57350707e+000 + <_> + 1.43560660e+000 -2.26509738e+000 -1.24877644e+000 -3.32225204e-001 + -3.99847984e-001 9.66871977e-001 + <_> + 1.45240605e+000 -2.29013634e+000 -1.20601082e+000 8.27516913e-002 + 5.02775535e-002 6.35417223e-001 + <_> + 1.25641286e+000 -4.43026245e-001 -8.43626678e-001 6.05957866e-001 + -1.12834446e-001 8.02493095e-001 + <_> + 1.54631054e+000 1.92438400e+000 -1.52924970e-001 -7.86084831e-001 + 2.88626522e-001 1.05809920e-001 + <_> + 1.54932582e+000 -4.34780151e-001 1.57423407e-001 5.19022763e-001 + -5.37223108e-002 4.18936044e-001 + <_> + 1.36883974e+000 -8.36824417e-001 -9.08970594e-001 -3.86486471e-001 + 1.66720009e+000 -7.49272823e-001 + <_> + -9.22789797e-002 -3.43154669e-001 -5.21501660e-001 2.54429197e+000 + 1.54782963e+000 -1.32725334e+000 + <_> + -4.70497757e-002 -3.47803801e-001 -5.61824813e-002 3.97736239e+000 + 2.72577000e+000 -1.97387540e+000 + <_> + 5.36191642e-001 -2.20846564e-001 2.41472697e+000 2.99363017e+000 + 2.26319218e+000 -1.77417421e+000 + <_> + -1.01755582e-001 -1.54092237e-001 -4.49457496e-001 3.37103677e+000 + 1.67261231e+000 -1.27512085e+000 + <_> + 3.57428581e-001 -1.22584569e+000 -1.84652328e-001 1.78011799e+000 + 1.06711435e+000 -4.72766757e-001 + <_> + -1.14678212e-001 -8.68290514e-002 -3.19254190e-001 3.69853044e+000 + 3.45812976e-001 -4.76046503e-001 + <_> + 4.51316535e-002 -3.79287481e-001 -8.71426165e-002 4.23237467e+000 + 1.13111854e+000 -8.57491910e-001 + <_> + 5.51698804e-001 -1.11371505e+000 3.74467373e-001 2.44535255e+000 + 2.17819023e+000 -1.73015118e+000 + <_> + 7.99382508e-001 -1.07948339e+000 1.44377744e+000 2.41766787e+000 + 9.39422250e-001 -4.00234818e-001 + <_> + 7.14954674e-001 -1.23066103e+000 -1.14372706e+000 -3.79632235e-001 + 1.30750167e+000 -2.69498467e+000 + <_> + 1.24693620e+000 -1.18761313e+000 -7.69987464e-001 2.01291609e+000 + 1.29443216e+000 -1.60388887e+000 + <_> + 7.52861083e-001 -1.10450721e+000 -2.82294720e-001 9.02086198e-001 + 1.79449272e+000 -3.04229856e+000 + <_> + 5.63329160e-001 -9.65939403e-001 -5.70413709e-001 2.08462858e+000 + 8.29194069e-001 -1.03207135e+000 + <_> + 1.54243374e+000 -4.78244483e-001 2.11613488e+000 3.66688997e-001 + 5.05798459e-001 -1.12250470e-001 + <_> + 1.62711125e-002 -1.65740356e-001 1.41934678e-001 3.89484143e+000 + 1.50319505e+000 -1.32828283e+000 + <_> + 9.64361370e-001 -5.51473498e-001 7.60188401e-001 2.48190522e+000 + 7.34505057e-001 -8.85795593e-001 + <_> + 1.59283197e+000 -1.00432348e+000 1.88210821e+000 2.71042848e+000 + 1.49362135e+000 -1.13497376e+000 + <_> + 1.18016934e+000 -7.21596122e-001 1.46401942e+000 3.01151586e+000 + 3.57794046e-001 -5.63889682e-001 + <_> + 5.14223158e-001 -6.46487415e-001 1.23979938e+000 1.84013581e+000 + 7.31477380e-001 -1.18199253e+000 + <_> + 1.57689404e+000 -1.17854583e+000 1.27210641e+000 2.89954829e+000 + 1.43740690e+000 -1.72707045e+000 + <_> + 5.18530667e-001 -1.85092723e+000 -2.34677717e-001 3.45993251e-001 + -4.43748474e-001 7.76176929e-001 + <_> + 9.08363342e-001 -1.27483451e+000 1.11719906e+000 2.47283554e+000 + 8.63515794e-001 -5.93133092e-001 + <_> + 1.28656566e+000 -5.34603834e-001 -1.22163847e-001 1.84756136e+000 + 7.52418220e-001 -4.06868786e-001 + <_> + 1.14613974e+000 -2.39190340e+000 -3.04001987e-001 2.74328709e+000 + 1.98875636e-001 -1.66841373e-001 + <_> + 1.06903470e+000 -1.62736857e+000 -1.15786600e+000 1.21928668e+000 + 3.82842600e-001 -2.34301731e-001 + <_> + 1.15260100e+000 -1.26720452e+000 4.15880233e-001 2.88245583e+000 + 3.16131264e-001 -1.93336800e-001 + <_> + 1.08884943e+000 -1.95386899e+000 -4.06867862e-001 2.12567234e+000 + 5.66069722e-001 -3.81844103e-001 + <_> + 4.91393179e-001 -5.27308106e-001 9.02863443e-001 2.29395103e+000 + 1.55300331e+000 -1.09134924e+000 + <_> + 2.65677899e-001 -4.14469063e-001 2.58700013e-001 2.47910571e+000 + 6.31325722e-001 -4.65840310e-001 + + <_> + 1308 + -4.0205671562643722e-002 + + 1. 1. 2.8121466710687260e-001 4.9226063509397661e-001 1. + 4.6738739917950543e-002 8.6041298882399725e-002 1. + 7.3453567996514646e-001 1. 1. 1. 6.1083491747914787e-001 1. + 5.2758263538396433e-001 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1.2581372160516349e-001 4.1358329648390574e-001 + 1.9181699013506145e-001 5.1220532387084827e-001 + 3.6297395078007821e-001 4.2491305388788686e-001 1. + 4.9590751521098647e-001 4.7933273921467801e-001 1. + 6.5873030941694843e-001 1. 6.6287011807209706e-001 + 8.0573224685243927e-001 7.5408280688102869e-001 + 6.5737485455634892e-001 2.5989600530073831e-001 + 3.4498915645515799e-001 8.2957593421684250e-001 + 6.1818855613946544e-001 9.4118026385413778e-001 + 3.1366966480839603e-001 6.4427142542445615e-001 + 2.3340873546018237e-001 2.7199044408604078e-001 + 6.6297499163421658e-001 2.0472692359273290e-001 + 2.6594710330038640e-001 3.4303005600135933e-001 + 7.4880873627525368e-001 3.2859052616594442e-001 + 4.8911937048978232e-001 3.4395189762312711e-001 + 4.9415493241213082e-001 2.1368144892593791e-001 + 7.6872568168670230e-001 2.3786860696781084e-001 + 1.0964162805166371e-001 8.9892093006541371e-002 + 6.9428869346308797e-001 6.7366419848275005e-001 + 6.3381187222957558e-001 1. 8.9538684353488818e-001 + 8.2292685010347066e-001 2.0868365659757315e-001 1. 1. + 4.7941836624561929e-001 1. 7.8788439050265813e-001 1. + 2.9135985136231324e-001 4.4588871626430754e-001 + 8.0650262838768805e-001 2.4601259632731504e-001 + 3.7866990796152711e-001 6.9491263454415986e-001 + 4.0296385634515580e-001 4.4602340215905933e-001 + 8.5664451128992930e-002 8.2612543602809563e-001 + 4.8057225571688955e-001 1.1042631649799411e-001 + 8.6136984232146066e-002 2.4631022370933658e-001 + 1.8831887155788457e-002 3.2215280551959402e-001 + 6.9911014130592319e-001 1.9777592866739560e-001 + 3.8709918979044045e-001 1.3201107536224899e-001 + 3.9395480105051489e-001 3.9967225329597916e-001 1. + 6.8301386496150931e-002 4.6574464624732753e-001 + 6.8461620189602990e-001 3.2703733501407165e-002 + 2.4450860280971329e-001 5.9752020283842222e-001 + 4.7351187815714463e-001 1.1851216987851543e-001 + 7.3311055802347215e-003 3.3405016097359780e-001 + 4.1020465659069560e-001 3.2739420922538814e-001 + 2.9661341216437120e-001 1.1720919030147282e-001 + 1.8669608580758365e-001 1. 8.5980875802549128e-001 + 1.7089693564566316e-001 1. 1. 1.0675547797944891e-001 + 1.9909505781298731e-001 2.4310747266783767e-001 + 8.8405679368318513e-001 1. 4.4117156000671293e-001 + 3.7838737169089653e-001 8.3772996561127758e-002 + 1.7465694561172981e-001 3.8621206503931071e-001 + 6.8997754877466477e-001 3.0524798487908844e-001 + 1.8332562734260294e-001 5.5882407848409067e-002 + 1.8922753870962841e-001 1.5077916335228381e-001 + 4.1487364651623185e-002 7.2932791477276249e-001 + 2.5759175945486196e-001 1. 7.3380464105897347e-001 + 9.2513595190658637e-001 3.6461475287968420e-001 + 7.3285510296011125e-001 2.2583553802884907e-002 + 2.5853480166383347e-001 5.5634480795381513e-001 + 3.0600357190498739e-001 3.7404018230720293e-001 + 1.0312191474526194e-001 1.8493747826044549e-001 + 9.4970765984319494e-001 1.9848980993666063e-001 + 6.8706148403346257e-001 6.4893424772427255e-001 + 2.6900809420429267e-001 6.4343432924049659e-001 + 2.0438631969960214e-001 2.8830992462248534e-001 + 4.1230751348303224e-001 3.6850184541126089e-002 + 7.5005337268450278e-001 1. 3.6981919866261609e-001 + 6.0536389795229884e-001 2.4946060886651675e-001 + 7.4180172734266814e-001 6.8436639804274557e-001 + 8.3739232379681627e-001 8.0804860723225125e-001 + 2.7971538210547348e-001 4.9305634704294926e-001 + 1.3581692901180484e-001 7.6806451006513760e-003 + 4.3500219407252107e-002 4.0944372911797616e-001 + 1.6361670040730630e-001 2.1251426738175799e-001 + 8.3434385867400485e-001 1.1988046343568474e-001 + 5.7871642786609556e-001 6.5429931445597478e-001 + 5.5264697039438870e-001 6.8123126190106242e-001 + 1.7964245641550136e-001 1. 5.0465023900701900e-001 + 4.0537014266587351e-001 1.3628030456837038e-002 + 3.8829406947041339e-001 8.2677014694047826e-002 + 7.1103872132365520e-001 1.0450207420700015e-001 + 4.8364370789074657e-001 4.6164380813994980e-002 + 2.4023567633547696e-002 8.7267681475350956e-001 + 3.7517008123321666e-001 9.0836036875198845e-001 + 6.9043513420236369e-001 3.4428109212712227e-001 + 4.1875975545323890e-001 3.9447213852421098e-001 + 7.4378272140764456e-001 8.4559092465270269e-001 + 4.7630367513626237e-001 2.6039554663942377e-001 + 5.1316971190525640e-001 3.6192009034797901e-001 + 7.0213434638433558e-001 8.7988028362507864e-001 + 4.0131101757780635e-001 4.3062817815508142e-001 + 6.5782494610070452e-001 3.8784218538657672e-001 + 2.1143229690282830e-002 2.9572384556659814e-001 + 5.0775260287924306e-001 8.7422718384788611e-001 + 3.0348464798019925e-001 3.5180430867945656e-001 + 6.0942027535736532e-001 1. 3.0376325740140941e-001 + 2.7160682465827746e-001 4.7235403251474689e-001 + 1.3340815339495024e-001 2.2356508138315545e-001 + 5.7919241051676085e-001 2.3954987961272184e-001 + 1.6828897897355077e-001 4.7058729474962535e-001 + 1.4205758293767140e-001 3.8595083487890432e-002 + 2.6144183605958471e-001 6.2697929304059741e-001 + 6.4565365617042458e-001 4.7633842837809631e-001 + 6.3854310452718843e-001 2.3631500708992115e-002 + 1.7542035868603992e-001 4.1021369253406942e-001 + 5.2450679112672693e-001 6.4874935694238023e-001 + 7.2058634642436203e-001 3.5504354776802569e-002 + 6.1995480475633732e-002 9.5434097204381646e-002 + 1.1531623909054704e-001 2.5462015729845322e-001 + 3.4413666844557139e-001 1.1685269895789774e-001 + 8.3970746040261790e-001 2.1039691349213466e-001 + 4.5507135033091345e-001 1.8312919796512017e-001 + 4.7467941978415723e-002 8.2157367929570946e-001 + 4.3245593564777401e-001 5.5849264801892073e-001 + 9.1108019683893524e-002 6.0562591168159940e-001 + 4.1606389089033929e-001 1. 9.1737103610791759e-001 + 2.5036730762625102e-002 3.4837859781626894e-002 + 6.4264709593494229e-001 3.9447231312380732e-001 + 2.1681448399894573e-001 1. 1.9508523898476005e-002 + 1.6860047611843618e-001 3.7490447707196360e-002 + 3.0548759588572455e-001 2.9604304489098671e-001 + 5.9398634010989826e-001 7.4920731760722381e-001 + 6.4049101829116417e-001 5.8398693559932469e-001 + 3.8633024732062032e-001 4.2420497672342344e-001 + 6.2075853587666530e-001 4.0423079419605712e-001 + 5.0015057481844527e-001 1.2514725600441673e-001 + 4.4496678932419892e-001 4.0609795012113153e-001 + 2.9276596262945359e-001 6.1051122657967172e-001 + 5.2818320556365994e-001 2.2392657494308715e-001 + 2.7082989394902252e-001 5.9780512263949526e-001 + 2.8044140636903075e-001 5.1613930125527285e-001 + 5.2594697262331191e-001 9.9253115432550304e-002 + 5.1004752417927668e-001 1. 1.2040155889186155e-001 + 1.9805223170731714e-001 4.3690377314227835e-001 + 1.6574267001958479e-001 5.2716274305294228e-002 + 4.9447636201790185e-001 1. 2.3096320381212623e-001 + 1.0430815959041759e-001 6.2122389992348169e-002 + 8.9044878963142768e-002 7.8124484232870794e-001 + 1.5688424770002579e-001 1. 1.5254406649726779e-001 + 6.0167223485529009e-001 7.7912864743675070e-001 + 1.3734530375933318e-001 1. 1. 1. 4.7427459055601379e-001 1. 1. + 1. 1. 1. 1. 1. 1. 1. 4.6510337869043122e-001 + 2.8244674815850596e-001 1. 1. 1. 4.3542216016436169e-001 + 8.1472975847933327e-001 1. 3.6189988209133733e-001 + 6.9640634163935067e-001 1.2239922703958092e-001 + 3.1764674253642972e-001 7.4653484692743788e-001 + 5.1381584206630415e-001 2.8720520664907945e-001 + 5.0632128964114453e-001 7.0605414671625810e-001 + 2.4412628971059391e-001 6.1312328612724798e-001 + 2.1531757412269332e-002 3.0661007488085745e-001 + 1.5345263436641635e-001 4.8762326886177787e-001 + 8.8807430554033662e-001 6.1079227042589568e-001 + 1.3207564768584332e-001 1. 6.2365939981771992e-002 + 7.2963363142140059e-001 5.8998111306728485e-001 + 6.3442920091082611e-001 2.4328177976679638e-001 + 2.5743416910831463e-001 2.1164668087394517e-001 + 9.5953519043462376e-001 4.8682746091174983e-001 1. + 4.0677790909294420e-001 1. 1. 8.7133450445292004e-001 + 1.4084312991525752e-001 1. 2.1390708103238765e-001 + 8.5930275442241855e-001 7.9058155536399422e-001 + 5.0822360005305461e-002 3.4620784980082114e-001 + 5.2993399911751382e-001 9.7799302594143844e-001 + 6.1651007543867975e-001 4.1950728785189006e-001 + 1.5359956466478875e-001 7.4967895472493851e-001 + 5.9414005065446318e-001 7.4765238892207014e-002 + 3.0628053223601986e-001 2.1289943453123988e-001 + 1.7096281949569922e-001 1.1540120041525567e-001 + 2.5309992992134894e-001 6.0061605032070131e-001 + 1.6277558685030630e-001 3.0004708121628304e-001 1. + 5.6703714768839542e-001 7.9224699791116904e-001 + 1.6213447563656738e-001 2.7302978534284278e-001 + 4.8622458668578417e-001 4.7398415740357130e-001 + 5.3586329933548993e-001 5.3192173034675061e-001 + 2.8076783828274582e-001 1. 1. 9.5183881435508699e-001 + 7.9250508256424868e-001 1. 4.4711192455565502e-001 + 8.2261115754816450e-001 1. 7.2547628624911531e-001 + 4.6702475299135482e-001 3.3351093111263330e-001 + 2.1603859037258208e-001 2.1110013396479235e-001 + 4.0750308881044278e-001 3.6885994586203003e-001 + 5.9760939053187301e-001 8.7357978751212553e-002 + 3.9435511949005436e-001 2.1832712782650341e-002 + 1.7459014294256922e-001 5.9498623421464867e-003 + 2.4072703340299700e-001 9.0631048024000460e-001 + 1.3740070078586933e-003 5.0761912941673271e-001 + 2.8857288141854720e-001 2.7449915527787807e-003 + 4.8384022769775098e-001 1.4055261436567693e-001 + 5.0340001273296797e-001 8.5804094280172924e-002 + 3.5970327569582866e-001 6.0265869574135178e-001 + 1.7869972082781352e-001 1.6489725707965475e-001 + 4.0017347928795804e-001 1. 1. 5.1770664816790646e-001 + 3.0633556302818293e-001 4.4117323577727252e-001 1. + 7.8830913979718775e-001 2.4332714934890945e-001 1. 1. + 5.9167427902967729e-001 1.6585622203206205e-001 + 4.8597279792160875e-001 1.5581491023226113e-001 + 2.3988347837929230e-001 1.4929116921061525e-001 + 5.5828116065098153e-002 5.7333011411528356e-001 + 1.3092076984118484e-001 1.2352391576516937e-001 + 4.1715823888801967e-001 4.8092526657818913e-001 + 6.4516265171120938e-003 7.8626766131488446e-002 + 6.9315291538212009e-001 6.9505820825531262e-001 + 7.6728185919619307e-001 1.9238552425187833e-001 + 1.0168574026291180e-001 5.4375773271483512e-002 + 6.7776072443627700e-001 5.7371829608535929e-001 + 8.5735367652577110e-001 4.3473436639285984e-001 + 1.3092703916142159e-001 3.6253891753135342e-001 + 9.1659775850417136e-001 1.9751126791595280e-002 + 2.3127856043750203e-001 5.2199818685202504e-001 1. 1. + 4.6048347493325670e-001 9.6530979942659501e-001 + 8.1353642835789031e-001 4.5175214378137440e-001 + 6.7792952871629353e-001 1.6633430048750522e-001 + 1.0306974825595874e-001 1.7274812137049447e-001 + 6.7891108924977628e-001 7.4797604776352244e-001 + 4.6122978797166314e-001 4.7750393842054578e-001 + 2.5461919269249550e-001 4.8522383241752254e-001 + 1.9155946944516228e-001 8.0245642681136731e-001 + 7.0417401883837771e-001 6.8079717324417222e-001 + 5.8124613621174193e-001 5.0111116661392385e-001 + 4.3671165160389619e-001 2.5793921717083196e-001 + 1.1610349841060617e-001 9.2888429611020307e-001 + 2.8448739038039184e-001 1.2282493482428639e-001 + 3.4058237289012061e-001 2.0071558185743385e-001 + 5.2769248150959014e-002 5.0391327644723916e-001 + 4.2411867478450732e-001 1.5952043837744297e-002 + 3.3548218961275195e-001 1.4445607790115159e-001 + 4.8968823472791323e-001 1. 2.2053854840569437e-001 + 3.0328082713040394e-001 5.8720563695664751e-001 + 6.0399075971435223e-001 3.9157101105486913e-001 + 4.8898148598545704e-001 7.7303215322776397e-001 + 8.8039318626961327e-001 8.6735551444137127e-001 + 8.7392166121137971e-001 7.2022070835505736e-001 + 2.1588093965076266e-001 2.5578711120764841e-001 + 7.1766383639453135e-001 7.3573858976350393e-001 + 6.0176522716061542e-001 3.2282350183989933e-001 + 4.8213349137318928e-001 8.8614224657364837e-001 + 5.5025681207670907e-001 1.8158519838546577e-001 + 2.6301926656576063e-001 2.8220946348840048e-001 + 3.2573426571316400e-001 1.2572200254821986e-001 + 4.5136480616235086e-001 4.5386794788143853e-001 + 5.7391641613567779e-001 1. 1. 3.1701659995803272e-001 + 6.4863181261893510e-001 1.4728285964094623e-001 1. 1. + 4.9862260356495386e-001 2.9126738284169373e-001 + 6.4098056428597350e-001 6.0438160384280550e-002 + 3.8987015003983055e-001 1.1827574959530796e-001 + 2.1538797882849225e-001 4.1879699065283399e-001 + 6.4702241566035967e-001 7.1544650985409142e-001 + 4.9098484784464058e-001 5.5623466424495849e-001 + 1.1820939634971467e-002 1.4167901523821089e-001 + 2.6478797773176899e-001 8.6987470223317587e-001 + 8.0082547869154974e-001 1.4705208116017296e-001 + 9.1045276965799604e-001 1.4547106715763558e-001 + 9.5184764966908308e-002 5.2697963181659579e-001 + 4.8740838223482003e-001 4.2441452422900994e-001 + 2.9788164459734251e-001 1.4824970715020905e-001 + 3.6108894473539821e-001 3.5382848811935852e-001 + 8.1907742242285830e-001 2.3202055833017804e-001 + 2.1133608998251824e-001 2.0361057191436521e-001 + 1.8417575708215502e-002 3.1149221368384988e-001 + 4.8429374828983279e-001 5.6718138665467355e-001 + 6.0101763852260168e-002 1.7314258500129548e-001 1. + 5.0642737679458323e-002 7.4895677271020944e-001 + 1.7415453784762769e-001 1. 1. 1. 1.7498085850513941e-002 + 1.1803393175812020e-002 7.1070457920092156e-001 + 7.2432322208767796e-001 2.1417656419291620e-001 + 7.5614728016675892e-001 8.7753218101180683e-002 + 2.5276043511312357e-001 5.3015166946957870e-001 + 8.7943413658207281e-001 1.1863723352528428e-002 + 1.0969858505922439e-001 4.6703324883115044e-001 1. + 3.5479918358373058e-001 6.7794919432760059e-001 + 4.0683850441896069e-001 3.4853804544261674e-001 1. + 6.1424791181940532e-002 1.1760812213478705e-001 + 6.0292829722123431e-001 4.3063303485198062e-001 + 1.6948930443136662e-001 5.8892831168925108e-001 + 5.1571691542421927e-001 8.8769518800759550e-001 + 5.2170612773126757e-001 8.5696597672699126e-001 + 8.5097327096875519e-001 6.1607204699008300e-001 + 2.8942947541570690e-001 2.9271070608919014e-001 + 8.4622389658607855e-001 4.2753440698405815e-001 + 6.6186861984420176e-001 5.4940098872126253e-001 + 6.1334732177426110e-001 4.4974226316408639e-001 + 3.2429956899445861e-001 5.8202159726420843e-001 + 1.6428740252035798e-001 1.7617593019726321e-001 + 1.9709812618188202e-001 3.9644112059400209e-002 + 3.0883839672260865e-001 6.3396600596428021e-001 + 6.0496792732260253e-002 1. 2.5096514986550089e-001 + 8.1799291733968257e-002 4.2592469839326003e-001 + 3.1742305792032571e-001 1.5028650400193469e-001 + -1.8386318936614895e-001 -1.7087443987811887e-001 -1. + -1.1695763258640617e-001 -2.9590043848584113e-001 -1. + -3.1541326498396538e-001 -1. -1. -1. -1. -1. -1. -1. -1. + -7.1260078008097205e-001 -1. -1. -1. -1. -1. -1. + -7.9596085368895464e-001 -1. -1. -1. -8.1104126089009843e-001 + -9.7385105018214868e-001 -1. -1. -1. -1. + -2.8715273793280888e-001 -2.0156797657911915e-001 -1. + -8.2347146033059745e-002 -5.2127417908260953e-001 + -4.5148110832482757e-001 -6.4987965704087414e-001 + -2.5986160118677770e-001 -2.8113285623277656e-001 + -1.5357652829901533e-001 -6.3042818831791178e-002 + -3.5503286091609421e-001 -1.4376163219789265e-001 + -9.2090709061700615e-001 -1.5225547796668082e-001 + -1.7081299328016786e-001 -7.7889597847204117e-002 + -1.6006778380041675e-001 -7.2107963712458079e-001 + -4.8861997079411346e-001 -4.8795498977261187e-001 + -6.2570748405695542e-001 -1. -4.9834919707525394e-001 + -2.8640080057893191e-001 -8.6020789233855499e-001 + -5.0852569167218264e-001 -3.2530926134408644e-001 + -1.5861150767146934e-001 -2.2638820554731065e-001 + -7.3861910234904804e-001 -4.5508542432915600e-001 + -3.4237837909765190e-001 -8.3380115966956589e-002 -1. + -2.0214839229450954e-001 -8.1182483564286992e-002 + -8.4619924914611533e-001 -2.4127525258363014e-001 + -6.0798951660711842e-001 -4.4081256444161104e-001 + -1.7005252207694926e-001 -4.2811522319499867e-001 + -6.4836305816347417e-001 -2.9089155528733968e-001 + -2.5912961914744803e-001 -4.9500413667993584e-002 -1. + -1.2969450690079825e-002 -6.8321807716188898e-001 + -3.6673428508620520e-001 -1.2718066136011791e-001 + -2.1809702887976234e-001 -1.1013925217002070e-001 + -8.6694637033949695e-002 -2.9899235789075573e-001 + -8.9898534327612582e-003 -4.0160209388107621e-001 + -3.3551997553076890e-001 -5.3104158997867718e-002 + -1.7317925834211670e-001 -5.9598782035818099e-001 + -5.5057254142558554e-001 -2.1271809557615720e-002 + -6.8095168209507462e-001 -1.2720282662667801e-001 + -3.0100768009504791e-001 -1.8020275422316892e-001 -1. + -4.7038673877334086e-001 -3.3072007838014422e-001 + -3.7248159055180780e-001 -1. -1. -1.3249458876412448e-001 + -8.4261030946910243e-001 -1. -1. -4.5077217167077538e-001 + -4.5779857785439387e-001 -3.6329107449769632e-001 + -2.1455537685072140e-001 -1.9307743761361854e-001 + -5.7139353456356223e-001 -2.1690109178359809e-001 + -2.9941062288076226e-001 -4.4169189490939322e-002 + -2.4631977733955968e-001 -3.8488692804238539e-001 + -4.5261009959236198e-001 -1.3354345709969534e-001 + -9.5156840175800061e-001 -6.5724879365087008e-001 + -4.4377351092510431e-001 -4.2511881971937060e-001 + -8.8352167531373604e-001 -8.3750413846754002e-001 + -6.0275861909901740e-001 -7.1700993352911779e-001 + -2.8086070805450392e-001 -7.3886388531013303e-001 + -1.5858685569346459e-001 -5.1697472728460814e-002 + -1.0968255167671216e-001 -1.2518072353201343e-001 + -4.4896767120196002e-001 -2.7511309815962104e-001 + -6.5899592368735471e-001 -8.1760178750748591e-001 + -4.1663937177290322e-001 -4.4816744162530214e-001 + -7.3836040767708191e-003 -8.7255356876574910e-004 + -2.8617208505347175e-001 -2.9773888971038172e-002 + -5.6255660022850718e-001 -5.5435354638786360e-001 + -5.1050028391737479e-001 -7.7727556117726110e-002 + -6.7694238946457957e-002 -4.4041097597197554e-001 + -3.3315964393854419e-001 -7.6657293361463008e-001 + -6.9832667326837849e-002 -5.6481059187744498e-001 + -5.8450949286477416e-001 -6.2372325624495295e-001 + -1.7192230333768282e-001 -6.3722051935174356e-002 + -6.0553593611594891e-001 -3.2255321681008024e-002 + -5.1715435627112782e-001 -1. -1.3130951307922867e-001 + -2.5254182441262700e-002 -3.0874893863396126e-002 + -2.0438200968441050e-001 -1.5300752978971011e-002 + -3.6904312870936429e-001 -3.2118122800973631e-001 + -2.8159689995057857e-001 -4.1643011747615338e-002 + -4.3296793697683772e-001 -4.1177659405696704e-001 + -3.3235013389860746e-001 -1. -3.9761517352119474e-001 + -5.8021184908914436e-001 -2.1144847329785602e-001 + -2.9728676887417943e-001 -1.7882959670705051e-002 -1. + -5.3353804167526764e-001 -3.9138966166876848e-001 + -4.8347069570000267e-001 -4.6501648414255781e-001 -1. -1. + -4.0821207829256395e-001 -1.9894450690723919e-001 + -3.6544549701521667e-001 -1. -3.9891979125579907e-001 + -1.8865847858613863e-001 -1.5448060583734566e-001 -1. + -2.8783736551033494e-002 -7.6125639989382335e-002 -1. + -2.6857046653795406e-002 -2.1049073121946010e-001 -1. + -2.1282024723290317e-002 -7.2944110601133316e-001 -1. + -4.1647748672756341e-001 -7.9676533601420063e-001 -1. + -2.1082312914327087e-001 -5.2574887045477275e-002 + -5.1801996556855967e-001 -5.2499790469833274e-001 + -6.7591235528685056e-001 -5.2234489423597574e-001 + -6.9165051507729480e-001 -2.3122697839065054e-001 + -7.4060026942487656e-001 -1.9042360051016807e-001 + -2.7910144393670749e-001 -1.2765987459494446e-001 + -7.0190290497834665e-002 -2.7786484483604196e-001 + -1.3941218317611831e-001 -6.3299173047590895e-001 + -5.5417555253111495e-001 -1.3546403538912744e-001 + -4.2393659849087517e-001 -4.3055125029109478e-002 + -4.2453285931215551e-001 -4.1536745902747946e-001 + -6.4311293105661227e-001 -2.0071048123797387e-002 + -4.1872680784666810e-001 -1.0515123590562783e-001 + -5.6279624374146975e-001 -3.9752153322045813e-001 + -2.2688684097065431e-001 -6.3230284980471008e-001 + -9.9811760039335163e-003 -1.9604097019963446e-001 + -9.5391635791980656e-001 -1.5710756230005279e-001 + -6.1494775840948868e-001 -7.3883891577555827e-001 + -2.4365748776332938e-002 -1.4334109404272577e-001 + -1.2002696133001606e-003 -6.1266602817869176e-001 + -7.4378326112328774e-001 -1.0260831229597943e-001 + -2.9469113872036756e-001 -5.4982363746215890e-001 -1. + -1.1455810406781969e-001 -6.0438346081276384e-001 -1. -1. + -5.6843325194167627e-001 -1. -4.5531889635062411e-001 + -1.8307974720438666e-001 -6.0593148582658352e-001 + -3.1305048695514048e-001 -9.6000463951275472e-001 + -1.9017837882225139e-001 -1.2742658104785171e-001 + -1.6783745508740761e-001 -1.1115403711026985e-001 + -3.7337700581301031e-001 -1.8612744643600745e-001 + -1.5128210470329684e-001 -4.1783620503136509e-001 + -2.9786981877124363e-001 -3.4798716787946943e-001 + -3.9301608015810613e-001 -4.1373948427620799e-001 + -2.3696709269947852e-001 -6.4795282437825985e-001 + -1.9635629815992164e-001 -3.3871908934742634e-001 + -2.4602272522798344e-001 -8.6325907513072220e-001 + -6.0113615017071709e-001 -3.2895314705636480e-001 + -5.3577512668107830e-001 -6.4615394357886535e-001 + -3.4617837961976622e-001 -3.8905114851934475e-001 + -1.8428338071793857e-001 -3.7917935512978351e-001 + -9.6506013429736015e-001 -1.2480050374476881e-001 + -1.0143536006620375e-001 -1.7660994986958120e-001 + -8.4007746878697206e-002 -4.1486802662711514e-001 -1. + -2.0149577320618334e-001 -8.1976189212918671e-001 + -7.4664904534180815e-002 -7.7828850957013376e-002 -1. + -5.3574793651467911e-001 -1.2084896257900850e-001 -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -7.0167508643019871e-001 -1. -1. -1. -8.4440529935266262e-001 + -1. -1. -1.8840382805285083e-001 -1. -1. + -3.1974171621255759e-001 -8.5935716799875217e-002 + -1.2101876287927525e-001 -3.1316660543872959e-001 + -2.3967976159110954e-001 -1.1962729791748671e-001 + -2.8204336088200627e-001 -9.9869161254798611e-001 + -2.1865121349258018e-001 -6.5846070384770750e-001 + -9.5367067292678165e-001 -2.9044981305172385e-001 + -8.8808107847990803e-001 -1.5370587431048952e-001 + -8.4891224488604672e-002 -4.8567548317596895e-002 + -2.7623188604594634e-001 -5.2009392711028091e-001 + -2.8463471150502068e-001 -8.4548767655623447e-001 -1. + -5.6697014120263545e-001 -8.3575978885867896e-001 + -6.7095706405955124e-001 -5.9728126926239444e-001 + -8.5340517328540400e-001 -1. -6.8342698453416051e-001 -1. -1. + -8.8225905763732992e-001 -1. -1.4437820505684834e-001 + -9.8107213169981700e-001 -1.6940082636726780e-001 + -8.1785501535776642e-002 -2.9091426687545363e-001 + -1.6175973392565587e-001 -5.7949215996514014e-001 + -3.1833797725048041e-001 -6.3517303781563883e-001 + -3.4049746419959126e-001 -6.9571969070851350e-002 + -4.6104056079852568e-001 -7.0664245456372499e-001 + -3.7041312052059588e-001 -3.9012266801001660e-001 + -4.8852386623537730e-001 -5.4053373265664439e-001 + -1.0653537559857022e-001 -4.6123090741829809e-001 + -8.7399549324909043e-001 -5.3263664433431068e-001 + -8.7035414654311005e-001 -4.0804211335797036e-001 + -5.4821661398838217e-001 -7.7014821809430067e-001 + -1.4788498057651714e-001 -7.4069349562274400e-001 + -7.4736588450012365e-001 -5.6069774594488220e-001 + -3.5194815769092913e-001 -5.9803588445490205e-002 + -3.2277043320466681e-001 -5.8422479496571023e-001 + -1.3466014488228750e-001 -7.5822294367024967e-001 + -8.1009099846679000e-001 -1.4078988204543808e-001 + -1.0456282853278273e-001 -2.8026226936125082e-001 + -2.7168375085206398e-001 -9.0412951639188477e-001 + -8.6524967326115743e-001 -4.4455080132499919e-001 + -9.8495197398828649e-001 -5.8711667572814263e-001 -1. + -9.8560463151850197e-001 -5.5598161468918761e-001 + -3.8358236723797967e-001 -5.8208831235212655e-001 + -6.3127249628009352e-001 -8.2992530539553444e-001 + -3.5135016702907557e-001 -4.8724333091636413e-001 + -3.0081143992937220e-001 -2.7291200074998678e-001 + -4.1141489340046172e-001 -4.7742748420524389e-001 + -7.7483939158618287e-001 -3.4814972281372225e-001 + -5.9607047784425098e-002 -1.4448772823537528e-001 -1. + -2.4813203029642075e-001 -4.5353464241124697e-001 + -1.8833021293752505e-001 -3.6673206529995916e-001 + -4.7288331349689361e-001 -3.4169009202599299e-001 + -4.2818167914167393e-001 -1.0867455018966972e-001 + -2.7377239099368050e-001 -2.5441882581019953e-001 + -2.2044798622170729e-002 -2.6919175120324434e-001 + -4.8788065935726471e-001 -5.1679386313179498e-001 + -1.6506406478547542e-001 -4.9247779195433233e-001 + -3.6788661631166009e-001 -6.6872545685880094e-002 + -3.1282734613370816e-001 -9.7085817084909110e-001 + -4.1067027436877201e-001 -2.7667582693645881e-001 + -5.4164296932596956e-001 -5.8875240736547430e-001 + -2.1718945517004273e-001 -1.1669702743191325e-001 + -5.4086579648205013e-001 -1.6485317981842507e-001 + -4.5021301741563025e-001 -1.4312424651456510e-001 + -1.9481726240750588e-001 -4.8984410791561678e-001 -1. + -1.9041843549912571e-001 -3.7804347313850029e-001 + -9.4937633812203825e-001 -7.3941917510000665e-001 + -3.5614651587890278e-001 -1.6954102914702310e-001 + -7.5697092853504666e-001 -7.6281748036685570e-001 + -6.5324442410672834e-001 -6.3033985543441229e-001 + -6.4649190995904793e-001 -1.0318284636535016e-001 + -6.6925686927843708e-001 -1. -3.4796001016720940e-001 + -5.0303223885357584e-001 -1. -1. -4.7242693892029985e-001 + -6.1522544719437489e-001 -5.8881436156207290e-001 + -2.0935273884583636e-001 -6.7164716233233807e-003 + -2.4140767083705228e-001 -7.9266948862064135e-001 + -9.7778203574697520e-001 -8.3080538475991705e-001 + -6.2516940628235118e-001 -1.3690471579329500e-001 -1. -1. -1. + -1. -1.5236752157465125e-001 -1. -1. -6.6326460605422521e-002 + -1.1868615664306607e-001 -4.9322338787041747e-001 + -6.2767711381935309e-001 -1.9183340571352575e-001 + -5.2462518526506530e-001 -1.4630556884122867e-001 + -1.0621737511075628e-001 -2.6826962584363545e-001 + -2.3533786989922628e-001 -4.2908831325651188e-002 + -3.3574484208750333e-001 -3.3820924539578623e-001 + -3.8120096618605864e-001 -4.6976929289137176e-001 + -8.0351779720849253e-002 -1. -7.8751222461249692e-001 + -6.4468636828832504e-001 -3.8132692506412169e-001 -1. + -2.3346036722510963e-001 -4.0015355930042773e-003 -1. + -2.9236898705596470e-001 -7.8183126134829428e-001 + -4.8825202171754623e-001 -4.0314564260187347e-001 + -3.3690646277472530e-001 -7.6142987129337192e-001 + -3.4729538379148162e-001 -1.3438225577042848e-001 + -1.2033437207450841e-001 -1.3146265491306958e-001 + -3.5578002031029343e-001 -3.0308927566747129e-001 + -6.7334928195166244e-001 -1. -1. -1. -5.0620505843984676e-001 + -8.1558727270130704e-001 -1.7216461017659832e-001 + -7.1829277109904957e-002 -2.6589991880251529e-001 + -3.0911837655440083e-001 -6.9745556118384533e-001 + -5.9299962532595431e-002 -1. -8.2970635094481349e-001 + -7.8905877276322534e-001 -3.2910579489448183e-001 + -7.5134212352124230e-001 -6.8514231098332867e-001 + -3.1752085305775674e-001 -2.7680182253298979e-001 + -2.1194423884305963e-001 -5.3660535256491226e-001 + -6.6066561084242525e-002 -2.7345772933472662e-001 + -6.0437351247145510e-001 -6.2787039369644937e-001 + -1.5004457119250175e-001 -7.4700980978095544e-001 + -2.8515316735476692e-001 -3.3224217192210248e-001 + -6.5219350801424691e-001 -8.4860356061536080e-001 + -6.3713705797581477e-001 -1. -7.0172291035483148e-001 + -3.3930932896496256e-002 -8.0142018888267580e-002 + -5.9091798610503760e-002 -5.8019231344548639e-001 + -2.4945653112745295e-001 -6.4437306196031563e-001 + -1.3287644291163983e-001 -3.6988372173753420e-002 + -3.2436619672395972e-002 -4.6332096108927645e-001 + -1.4052436533691826e-001 -2.2894564155081851e-001 + -8.5314281991729999e-003 -6.3641163339234086e-001 + -1.2471554822757144e-001 -4.8414303966806110e-001 + -7.6897747861399759e-002 -1. -2.1636993212833666e-001 + -6.3572848132657911e-001 -1. -1.6971359514323203e-001 + -2.9169825639661928e-001 -1. -6.3777847385307973e-001 -1. -1. + -1. -2.4008838453449416e-001 -5.7667928208114017e-001 + -8.9562758107056051e-001 -3.6426581109467254e-001 + -5.3446126006796191e-001 -3.6058001448324267e-001 + -9.1014639958595753e-002 -1.2370239795616017e-001 -1. + -1.3328982796872743e-001 -1.8666386451771294e-001 + -7.0024327528346175e-001 -2.7819774377734885e-001 + -5.7213609002735720e-002 -8.8627293570401056e-001 + -3.6170151194625766e-001 -2.4728603210372652e-001 + -5.9450952405994434e-002 -2.9471874517951457e-001 + -1.1646761603050947e-001 -2.2025851217071957e-002 + -3.2941434873369757e-001 -3.1888160265128662e-001 + -4.9340148135525549e-002 -2.7400625227340336e-001 + -1.1774124647916734e-001 -3.9397247655852752e-001 + -4.9301430416209824e-001 -9.6791135357351632e-001 -1. + -4.0597288785027019e-001 -5.6082978669985795e-001 + -6.5009063133745415e-001 -5.3437916034501065e-001 + -7.9259885789694595e-001 -7.0371336871110163e-002 + -3.2864623702608808e-001 -4.7703638665023002e-001 + -5.5934528496016622e-001 -3.1573179892003443e-002 + -4.7060978479665355e-001 -4.4226598922756793e-001 + -4.2004516489739532e-002 -6.5924345344898805e-001 + -6.3226867419998867e-001 -2.3099420838833587e-001 + -7.1141562604675335e-001 -5.3907883525039890e-001 + -2.1963162463869385e-001 -5.5195455192482312e-002 + -8.3445360008044489e-001 -1.6155137213765455e-001 + -1.8144619194043743e-001 -2.3995362643180193e-001 + -4.9591676603264240e-001 -1.8689142038409759e-001 + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 + 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 + 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 + 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 + 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 + 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 + 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 + 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 + 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 + 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 + 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 + 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 + 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 + 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 + 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 + 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 + 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 + 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 + 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 + 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 + 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 + 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 + 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 + 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 + 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 + 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 + 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 + 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 + 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 + 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 + 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 + 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 + 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 + 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 + 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 + 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 + 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 + 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 + 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 + 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 + 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 + 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 + 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 + 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 + 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 + 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 + 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 + 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 + 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 + 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 + 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 + 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 + 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 + 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 + 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 + 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 + 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 + 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 + 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 + 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 + 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 + 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 + 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 + 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 + 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 + 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 + 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 + 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 + 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 + 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 + 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 + 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 + 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 + 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 + 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 + 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 + 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 + 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 + 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 + 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 + 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 + 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 + 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 + 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 + 1301 1302 1303 1304 1305 1306 1307
+
diff --git a/acii/acii-client/trainedClassifiers/SVM_UD_N00020.xml b/acii/acii-client/trainedClassifiers/SVM_UD_N00020.xml new file mode 100755 index 0000000..36f7c9e --- /dev/null +++ b/acii/acii-client/trainedClassifiers/SVM_UD_N00020.xml @@ -0,0 +1,1022 @@ + + + + C_SVC + RBF + 3.4720000000000001e-001 + 1. + 2.2204460492503131e-016 + 100000 + 6 + 6 + 2 + + 1 + 2 +
i
+ + -1 1
+ 296 + + <_> + -5.06185353e-001 -6.75871968e-002 4.18987542e-001 -1.21503256e-001 + -2.12089211e-001 2.95062680e-002 + <_> + -3.91815215e-001 -4.10272256e-002 3.33113641e-001 -1.38262942e-001 + 6.84815288e-001 -5.69620848e-001 + <_> + 1.16192055e+000 1.40462682e-001 2.76452065e-001 -1.33235860e+000 + 1.34338617e+000 -1.81969523e+000 + <_> + 7.99332201e-001 1.37484521e-001 -5.13213500e-002 -6.89140320e-001 + 6.24474943e-001 -6.34543538e-001 + <_> + 1.52400935e+000 -2.24580858e-002 1.52874947e+000 -1.63681221e+000 + 7.81002104e-001 -1.65286851e+000 + <_> + -9.37695384e-001 4.95544747e-002 -7.38715708e-001 -3.85915369e-001 + 6.52157009e-001 -1.56869888e+000 + <_> + 7.36403704e-001 1.10358644e+000 -8.42549205e-001 -1.29479122e+000 + -2.32682848e+000 -1.27856636e+000 + <_> + -4.48750585e-001 -1.10575356e-001 -1.79396942e-001 + -3.59910168e-002 6.34435475e-001 9.79303300e-001 + <_> + -3.87819767e-001 1.80571175e+000 1.77268875e+000 1.91875324e-001 + 4.36767161e-001 1.79693019e+000 + <_> + -1.18341637e+000 1.01750135e+000 2.02598596e+000 -7.50726521e-001 + 2.87522125e+000 1.59990966e+000 + <_> + -2.62961537e-001 1.22046196e+000 3.22696590e+000 -8.89549732e-001 + 6.18318260e-001 2.81071037e-001 + <_> + -9.31702197e-001 1.13287354e+000 1.99215388e+000 -7.68349648e-001 + 2.81010056e+000 1.66671443e+000 + <_> + -1.57422572e-002 6.47233605e-001 -1.63433576e+000 -1.15710247e+000 + 2.14674163e+000 -3.44026065e+000 + <_> + -1.05640180e-001 3.76842439e-001 -1.41477835e+000 -1.09711063e+000 + 6.33876994e-002 -2.62790084e+000 + <_> + -3.76832247e-001 2.81656116e-001 -8.78739774e-001 -1.36930811e+000 + 2.62242103e+000 -3.76315165e+000 + <_> + 6.60489857e-001 7.82977641e-001 3.43831420e-001 -1.62058282e+000 + 2.13764262e+000 -3.21712971e+000 + <_> + 6.43009722e-001 7.76978314e-001 5.32806158e-001 -1.03557515e+000 + 2.56236887e+000 -2.90308809e+000 + <_> + -1.62575528e-001 1.18772864e+000 1.37389489e-002 8.49887788e-001 + 4.84506249e-001 -2.82123387e-001 + <_> + 1.35420215e+000 7.24600852e-001 1.51428133e-001 -1.05715072e+000 + 8.76491725e-001 -1.74240398e+000 + <_> + 1.09549594e+000 8.61297548e-001 -6.14456058e-001 -6.47089779e-001 + 6.80016220e-001 -1.33656836e+000 + <_> + -2.71451890e-001 8.04869533e-001 -4.63888198e-001 4.71144378e-001 + 1.36846900e+000 -1.02479863e+000 + <_> + 6.46664426e-002 3.49702328e-001 2.33183056e-001 -4.34884429e-003 + 2.21953440e+000 -1.69180059e+000 + <_> + -1.68069288e-001 2.73647159e-001 2.76554823e-001 -1.09872550e-001 + 1.57553685e+000 -1.44591630e+000 + <_> + 4.46891226e-002 4.39442918e-002 1.51582336e+000 -9.88465488e-001 + 2.33691525e+000 -2.12247610e+000 + <_> + -1.64189577e+000 5.42194843e-001 -1.84627712e+000 -1.52248156e+000 + -6.29049897e-001 -1.40549946e+000 + <_> + -1.08636774e-001 -1.84956104e-001 -1.09010704e-001 + -4.87060159e-001 -8.40986729e-001 -1.14477348e+000 + <_> + -1.81054547e-001 -5.13540983e-001 -4.93742572e-003 + -7.00444102e-001 -1.29446805e+000 -1.18972921e+000 + <_> + -5.75359864e-003 1.30639151e-002 9.03629839e-001 -4.51423466e-001 + 2.20849797e-001 -1.47810769e+000 + <_> + -1.53086305e-001 1.22602791e-001 5.37428498e-001 -6.35705769e-001 + 6.27006471e-001 -1.87871373e+000 + <_> + -5.59125245e-001 4.85399932e-001 2.78934509e-001 4.30562198e-001 + 4.49843973e-001 2.01305342e+000 + <_> + -2.79442817e-001 2.77571231e-001 2.10732937e-001 1.87801018e-001 + 2.26318002e+000 9.31431353e-001 + <_> + 1.28428161e+000 1.42360604e+000 9.26786244e-001 -1.07612717e+000 + 3.99058349e-002 -8.58307481e-001 + <_> + -5.82099140e-001 5.37912548e-001 -5.41397631e-001 4.19992357e-001 + 1.08272457e+000 4.82059002e-001 + <_> + -6.95969880e-001 1.18970501e+000 9.22582924e-001 -9.20050085e-001 + 3.50679612e+000 -2.77105427e+000 + <_> + -1.40816116e+000 2.03369570e+000 -1.07508290e+000 -8.15836489e-001 + 2.08894396e+000 5.68068624e-001 + <_> + -1.00761604e+000 -9.01303351e-001 2.98478961e+000 -1.24552453e+000 + 1.64823127e+000 -1.93060112e+000 + <_> + -8.67275357e-001 1.60557985e+000 -2.21799159e+000 -1.18066442e+000 + 2.46901774e+000 -2.46155024e+000 + <_> + -1.18391585e+000 2.42633891e+000 5.76876283e-001 -1.25690985e+000 + 3.20732307e+000 -2.32866263e+000 + <_> + -9.02235627e-001 2.80301952e+000 -1.41212201e+000 -8.98889959e-001 + 1.63154590e+000 -1.10143566e+000 + <_> + -9.31202769e-001 3.23322248e+000 1.14450681e+000 -1.47294021e+000 + 1.07682776e+000 -6.28527641e-001 + <_> + -9.77649987e-001 3.01835346e+000 -1.13287497e+000 -6.31599784e-001 + 9.54467535e-001 -6.19730234e-001 + <_> + -2.64959276e-001 3.01679015e+000 1.57310688e+000 -3.89429092e-001 + 3.51094222e+000 -2.42996335e+000 + <_> + -1.82768476e+000 1.82829475e+000 -3.03082633e+000 -9.51588452e-001 + 7.18169093e-001 -4.11831975e-001 + <_> + -3.61349821e-001 1.46371439e-001 -6.48967981e-001 6.74830526e-002 + -8.90057683e-001 1.16114867e+000 + <_> + -2.15031862e+000 -3.95283532e+000 4.66499358e-001 5.79760909e-001 + -1.54232574e+000 1.50996423e+000 + <_> + -1.80421150e+000 -1.35689068e+000 -1.02312267e+000 + -6.98425353e-001 1.50266826e+000 6.30513787e-001 + <_> + -1.05056727e+000 1.12625360e+000 2.03174889e-001 -1.05512619e+000 + -2.04259133e+000 7.89481282e-001 + <_> + 1.50303316e+000 -1.63121551e-001 2.17344500e-002 -1.45489132e+000 + -7.23685324e-001 -2.91798997e+000 + <_> + -1.30877399e+000 2.55193877e+000 1.74720538e+000 -3.91158521e-001 + 1.56806850e+000 -1.11439705e+000 + <_> + -1.03258765e+000 1.08469307e+000 -1.94608128e+000 -1.34815848e+000 + 2.80993605e+000 -2.32991529e+000 + <_> + -1.73528969e+000 6.93781614e-001 -7.09316313e-001 7.40978777e-001 + -2.76050717e-001 1.31083190e+000 + <_> + -2.71451890e-001 1.04154634e+000 1.35339391e+000 4.10662830e-001 + 1.19341433e+000 -8.14520538e-001 + <_> + -1.80720806e+000 6.36260867e-001 -8.04326773e-001 9.02921438e-001 + 4.59161848e-001 2.30601263e+000 + <_> + -1.79372334e+000 3.06923270e-001 2.73200095e-001 7.42073357e-001 + -1.49804604e+000 2.14064574e+000 + <_> + -1.40101045e-001 -3.13207179e-001 1.33195579e+000 8.27617124e-002 + -1.55160248e-001 9.92935002e-001 + <_> + -1.00162280e+000 7.93957174e-001 -2.65690351e+000 -1.74190927e+000 + 1.09865642e+000 -1.16111672e+000 + <_> + -1.88062465e+000 -1.30734384e+000 -5.01038015e-001 5.10179818e-001 + -8.45771968e-001 4.35989320e-001 + <_> + -1.31127119e+000 -1.02728248e+000 2.16122121e-001 3.09176356e-001 + 3.78847629e-001 2.33276188e-003 + <_> + -1.82119215e+000 5.61384499e-001 1.43295372e+000 -1.92679811e+000 + 1.52624655e+000 -2.15156817e+000 + <_> + -1.04607236e+000 1.56447744e+000 2.80030823e+000 -1.22418499e+000 + 1.44519222e+000 -8.32458138e-001 + <_> + -1.32275820e+000 2.29384041e+000 -6.43902957e-001 -2.38529608e-001 + 2.66924453e+000 -2.20775580e+000 + <_> + -1.50005686e+000 1.44081259e+000 -2.98334622e+000 -1.72967598e-001 + -1.11535408e-001 3.48459274e-001 + <_> + -1.10135071e-001 1.67518631e-001 2.70338148e-001 -8.38576758e-004 + -2.41735801e-001 6.31294668e-001 + <_> + -2.18012586e-001 9.67497975e-002 1.07791281e+000 2.29038581e-001 + -3.60106379e-001 6.86249852e-001 + <_> + -2.14516550e-001 5.29489703e-002 5.21089613e-001 1.59278810e-001 + -3.17814201e-001 7.44326532e-001 + <_> + 2.73676054e-003 7.77560174e-002 1.85420871e-001 -8.26718062e-002 + 1.32066935e-001 5.91068208e-001 + <_> + 2.58446395e-001 5.75101972e-002 2.19816819e-001 -1.10054566e-002 + 1.98340431e-001 -2.12313700e-003 + <_> + 2.41465688e-001 9.08568949e-002 1.84234992e-001 1.14509411e-001 + -3.11719924e-001 4.57899004e-001 + <_> + -2.93926388e-001 1.58606425e-001 5.22889495e-001 1.61492869e-001 + -6.58129036e-001 7.13822305e-001 + <_> + 7.68866837e-001 1.66607827e-001 3.92953396e-001 -2.98545472e-002 + 1.76487893e-001 5.62367499e-001 + <_> + 6.79967761e-001 5.50905429e-003 -7.46081412e-001 -9.15198267e-001 + 6.76179767e-001 -5.05013645e-001 + <_> + 1.75075185e+000 -1.29361913e-001 1.42643130e+000 -3.97295326e-001 + -5.16269326e-001 2.77182297e-003 + <_> + 1.76323771e+000 -1.12501159e-001 1.71860707e+000 -8.14692318e-001 + -3.67596708e-002 -2.39869952e-001 + <_> + 1.29327130e+000 -8.69414583e-002 5.70662260e-001 -6.48497224e-001 + 1.90119356e-001 -3.38655472e-001 + <_> + 1.57395267e+000 -8.71328264e-002 9.72624660e-001 -4.88884002e-001 + -3.77196848e-001 1.20800510e-001 + <_> + 1.65885627e+000 -2.29506698e-002 1.14731872e+000 -5.96200287e-001 + -4.72426057e-001 1.68133736e-001 + <_> + 1.56746006e+000 -1.41552359e-001 1.64417040e+000 -5.67313790e-001 + -1.40086338e-001 -4.69810590e-002 + <_> + 5.83077788e-001 -7.05302507e-002 -1.13564467e+000 -1.25998640e+000 + -3.16104859e-001 1.30226631e-002 + <_> + 1.14294207e+000 8.40599760e-002 -1.74516365e-001 -8.86089981e-001 + 7.63875172e-002 -3.78502399e-001 + <_> + 1.20686948e+000 -9.80482176e-002 -1.52124226e-001 -4.09699649e-001 + 2.13649735e-001 -3.01927447e-001 + <_> + 1.78121734e+000 3.09879780e-002 7.00445592e-001 -1.04997456e+000 + 5.53393245e-001 -9.67674971e-001 + <_> + 1.75624561e+000 -5.16991541e-002 1.19968736e+000 -1.10049355e+000 + 9.26329613e-001 -1.12659705e+000 + <_> + 1.70330572e+000 -3.98571007e-002 5.81741035e-001 -9.48762357e-001 + -1.35479689e-001 -2.23344982e-001 + <_> + 1.74226153e+000 -8.36639702e-002 1.32191002e+000 -5.77736914e-001 + -3.90413791e-001 -4.08418179e-002 + <_> + 3.68321627e-001 -8.30589011e-002 3.43944319e-002 -2.57161498e-001 + 9.10455763e-001 -8.91321361e-001 + <_> + -6.59511268e-001 -9.09017697e-002 -1.50097108e+000 + -2.89795488e-001 7.69335330e-001 -8.17975938e-001 + <_> + 5.21806180e-002 -3.68655294e-001 -7.65529931e-001 -9.55806196e-001 + 7.58240700e-001 -3.67418706e-001 + <_> + 2.37969652e-001 -5.62057078e-001 -2.88350821e-001 -9.11937177e-001 + 3.57814699e-001 -1.57562315e-001 + <_> + 4.21261519e-001 -4.36248511e-001 -7.73944438e-001 4.69786189e-002 + -8.71336520e-001 9.60290968e-001 + <_> + -3.18398595e-001 1.06157102e-002 -9.11796987e-001 5.77846281e-002 + -1.66931018e-001 9.00497258e-001 + <_> + -4.52746034e-001 7.24708214e-002 -6.10988021e-001 -5.73540218e-002 + 3.28243226e-001 1.14073940e-001 + <_> + -3.18398595e-001 5.69906056e-001 8.80370677e-001 5.44798076e-001 + 5.64950645e-001 1.84784389e+000 + <_> + -1.82269049e+000 -5.23675025e-001 8.47629726e-001 3.88491184e-001 + -7.51405060e-001 1.65422201e-001 + <_> + -1.69683337e+000 -5.60895264e-001 1.45673466e+000 8.25408816e-001 + -1.11213017e+000 2.41654015e+000 + <_> + -1.94355321e+000 -1.73628247e+000 2.02709532e+000 3.00334960e-001 + 3.22144914e+000 3.33439517e+000 + <_> + -1.69933057e+000 -1.88864350e+000 2.05998659e+000 3.46184790e-001 + -3.30787569e-001 2.76934481e+000 + <_> + -2.11186218e+000 -4.84552479e+000 1.67991030e+000 -2.53459394e-001 + 5.64606309e-001 2.06623816e+000 + <_> + -1.82269049e+000 -9.75065827e-001 2.37707901e+000 -5.79179108e-001 + 3.54026532e+000 2.61402512e+000 + <_> + -1.70732152e+000 -1.21426070e+000 1.42074168e+000 -5.35443068e-001 + 1.68195701e+000 2.16611409e+000 + <_> + -1.90809345e+000 -9.55817640e-001 8.57849419e-001 5.83568215e-001 + 1.48075253e-001 2.65164495e+000 + <_> + -1.91708326e+000 -1.73919570e+000 1.60071158e+000 -5.75771987e-001 + 3.47684574e+000 3.03105998e+000 + <_> + -2.11286116e+000 -5.32021618e+000 1.80177188e+000 -4.95443851e-001 + 1.78838992e+000 2.84269619e+000 + <_> + -2.05342865e+000 -5.13133526e+000 2.37949038e+000 2.51187161e-002 + 4.94335508e+000 3.43637776e+000 + <_> + -1.74727607e+000 -4.29377270e+000 2.83601379e+000 -1.06718652e-001 + -8.62877369e-001 1.34631348e+000 + <_> + 9.41329822e-002 1.21129549e-003 9.12234128e-001 -1.01820789e-002 + 2.79858798e-001 -3.07232499e-001 + <_> + -1.60078362e-001 2.78931838e-002 1.62709847e-001 -2.30540633e-001 + 2.07744867e-001 -4.38751429e-002 + <_> + 1.62214488e-002 6.03332222e-002 5.33897161e-001 -3.33000183e-001 + -1.18862057e+000 7.19486356e-001 + <_> + -2.46480256e-001 1.33660853e-001 -7.13785768e-001 -2.55953312e-001 + 1.15839489e-001 -1.18542649e-001 + <_> + 5.23145854e-001 1.02640414e+000 -1.19295716e+000 7.45769024e-001 + 3.66825134e-001 1.21647101e-002 + <_> + -2.25756830e-003 9.07982528e-001 -3.85074258e-001 7.35519230e-001 + 5.17253041e-001 -2.20527425e-001 + <_> + -5.64619005e-001 5.64317763e-001 -1.68323112e+000 6.54452993e-003 + -1.14571810e+000 1.84621528e-001 + <_> + -1.64689004e+000 -2.50756598e+000 1.50629413e+000 -1.21098530e+000 + 8.32455516e-001 1.46098220e+000 + <_> + -1.31726444e+000 -1.89319110e+000 1.89756501e+000 -1.18465614e+000 + 5.50678194e-001 -6.78963959e-001 + <_> + -1.72430217e+000 -2.15314555e+000 1.44009006e+000 1.74851060e-001 + 1.45595109e+000 1.81182134e+000 + <_> + -1.16643560e+000 -2.04347301e+000 2.89810389e-001 -2.20067695e-001 + 3.26568097e-001 -1.20135203e-001 + <_> + -1.07853544e+000 1.46026754e+000 2.89634371e+000 -1.26925349e+000 + 9.24555004e-001 -9.13158536e-001 + <_> + -1.65637934e+000 2.22457290e+000 1.01381183e+000 -8.73399913e-001 + 2.20576239e+000 1.78738248e+000 + <_> + -1.74128294e+000 1.85230768e+000 1.16108823e+000 2.18375877e-001 + 3.36641002e+000 2.25602889e+000 + <_> + -6.57513499e-001 4.92522240e-001 -5.06016076e-001 3.66489708e-001 + 2.21262977e-001 -1.20063396e-002 + <_> + -8.20328653e-001 3.22454602e-001 1.35376513e-001 4.18139309e-001 + -3.70964199e-001 6.76211536e-001 + <_> + -1.68684471e+000 2.49253368e+000 1.24836934e+000 -1.29078948e+000 + 3.64233911e-001 4.41536829e-002 + <_> + -4.97036949e-002 2.41885796e-001 -9.36141908e-001 -2.78421074e-001 + -2.98641741e-001 7.18686044e-001 + <_> + -1.80920577e+000 3.68972033e-001 -8.98478150e-001 4.31237161e-001 + -1.20046534e-001 9.34926867e-001 + <_> + -1.07603824e+000 2.36211807e-001 2.44512215e-001 4.18360263e-001 + -7.39362955e-001 1.29401481e+000 + <_> + -1.85615242e+000 1.90840233e-002 -1.71829903e+000 -2.71330401e-002 + -1.44588709e-001 -5.74779585e-002 + <_> + -5.47638297e-001 4.05952930e-001 2.19648194e+000 -2.46165678e-001 + 1.60650623e+000 1.23199880e+000 + <_> + -2.08639121e+000 -4.90294755e-001 -2.72776151e+000 + -8.95665288e-001 6.56178687e-003 1.61998534e+000 + <_> + -7.67388761e-001 5.74632227e-001 -1.21087992e+000 1.72400102e-001 + 8.73669863e-001 -5.47896743e-001 + <_> + -1.72879708e+000 2.44505361e-001 -2.26259581e-003 8.25357139e-001 + -4.61121291e-001 7.16303170e-001 + <_> + -1.08452868e+000 4.78790253e-001 1.06843889e+000 6.60165370e-001 + -4.34829980e-001 7.24680364e-001 + <_> + 1.94518998e-001 -3.58467638e-001 2.15738201e+000 -6.63298428e-001 + 1.53841332e-001 -2.36214384e-001 + <_> + 1.11613132e-001 -4.46671695e-001 1.64303195e+000 -9.04550076e-001 + 4.52827752e-001 -7.51577139e-001 + <_> + -6.47524893e-001 4.10082728e-001 -1.19009793e+000 -2.05131657e-002 + -1.21761537e+000 1.07699680e+000 + <_> + -1.71780956e+000 2.37629294e+000 -2.06547093e+000 -4.64660019e-001 + -2.01523930e-001 4.43185329e-001 + <_> + -1.86414337e+000 -1.32320654e+000 1.24400489e-001 4.05872375e-001 + 7.67974257e-001 -9.57645714e-001 + <_> + -1.73828626e+000 -1.02319174e-001 6.85848221e-002 2.43717089e-001 + 9.77870822e-001 -9.56307352e-001 + <_> + -1.15345037e+000 2.58288324e-001 1.84842455e+000 1.50242835e-001 + 9.04499590e-001 -9.06941414e-001 + <_> + -1.71231580e+000 -1.80482435e+000 -1.09556699e+000 3.11632127e-001 + 1.20198488e-001 2.07961261e-001 + <_> + -1.38069236e+000 -1.12834215e+000 -6.14643216e-001 + -5.05949780e-002 -6.89249635e-001 1.58945858e+000 + <_> + -1.39417708e+000 -8.07094634e-001 -1.41401187e-001 7.11705983e-002 + -4.76460874e-001 8.11825454e-001 + <_> + -9.71157372e-001 -1.15935314e+000 7.28790879e-001 -5.46903133e-001 + -7.52318621e-001 1.66938469e-001 + <_> + -8.72769117e-001 9.51512694e-001 -1.37606049e+000 6.83014572e-001 + -2.10126098e-002 6.22528195e-001 + <_> + -1.74328065e+000 7.89885163e-001 -2.50821185e+000 8.32636096e-003 + 2.86062658e-001 -2.12548643e-001 + <_> + -1.31526661e+000 1.07516515e+000 -1.87523913e+000 1.65612012e-001 + -1.47102141e+000 1.42213559e+000 + <_> + 3.78809720e-001 -4.54748385e-002 2.28563368e-001 -9.80962254e-003 + 3.36416423e-001 -5.51815592e-002 + <_> + 4.53225225e-001 1.62326917e-002 1.23006418e-001 5.85001335e-002 + -8.88568163e-002 1.49958059e-001 + <_> + 1.27229524e+000 -1.00847460e-001 1.11141014e+000 -3.00289597e-002 + -3.15913081e-001 -3.75282705e-001 + <_> + 3.61329585e-001 -1.39279678e-001 6.61074162e-001 1.49025247e-001 + -1.90827176e-001 1.68038812e-002 + <_> + -9.58671570e-001 -9.79210138e-002 -9.58605558e-002 7.97967792e-001 + -6.77050591e-001 1.30189872e+000 + <_> + -1.01610637e+000 -6.06555231e-002 3.04204002e-002 7.37700164e-001 + -7.22373903e-001 1.05710673e+000 + <_> + -9.45686281e-001 -1.54098749e-001 -2.40572810e-001 6.31457746e-001 + -1.46464527e-001 6.82456315e-001 + <_> + -8.73767972e-001 -1.99608341e-001 -2.05038413e-001 5.95564604e-001 + 7.84421936e-002 3.99472803e-001 + <_> + -1.30677629e+000 4.23306488e-002 8.92830372e-001 1.23893750e+000 + -4.36565012e-001 1.34579539e+000 + <_> + -1.71531236e+000 4.44315895e-002 -7.80909538e-001 1.25896752e+000 + -7.85333633e-001 1.44290257e+000 + <_> + -1.65538049e+000 -7.51480982e-002 -2.21352482e+000 1.00811529e+000 + -6.54779196e-001 9.34630811e-001 + <_> + -1.74028409e+000 1.74047053e-002 -2.06380272e+000 1.49385381e+000 + -4.77900267e-001 1.68044662e+000 + <_> + -2.57309135e-002 -9.86272395e-002 -6.37359500e-001 1.73819423e-001 + -2.49329612e-001 6.84509337e-001 + <_> + -9.49681759e-001 -1.37571013e+000 6.83474183e-001 9.98243928e-001 + -7.69191027e-001 9.03941870e-001 + <_> + 1.82816398e+000 3.87147099e-001 9.16621536e-002 8.72646451e-001 + -2.67152190e+000 -1.46193469e+000 + <_> + 1.51452017e+000 -9.34484899e-002 -7.16521204e-001 9.54778314e-001 + -2.88277864e+000 -1.78798676e+000 + <_> + -4.82053943e-002 -5.14834166e-001 8.36809158e-001 1.23184168e+000 + -1.40895605e+000 3.38460612e+000 + <_> + 2.97402173e-001 -5.67643106e-001 1.39059389e+000 1.24360895e+000 + -1.35409379e+000 3.56690049e+000 + <_> + 2.27140766e-002 -6.99408293e-001 1.01581728e+000 9.24993634e-001 + -4.62472409e-001 3.20400500e+000 + <_> + 2.56948113e-001 -4.15408194e-001 2.66723609e+000 1.68300200e+000 + -9.25593615e-001 2.56969929e+000 + <_> + -7.41759017e-002 -1.06469348e-001 -1.28052294e+000 3.20083164e-002 + 1.58220872e-001 -5.51733468e-003 + <_> + -1.82394218e-002 -1.09112322e-001 -1.82532951e-001 + -5.41207135e-001 -1.26107705e+000 1.30751050e+000 + <_> + -3.11406523e-001 -6.30022883e-001 -7.05236852e-001 + -2.62935430e-001 -1.13140035e+000 1.05197644e+000 + <_> + -2.26502940e-001 -4.93289530e-001 -6.01021290e-001 + -3.91235024e-001 -1.14026093e+000 9.18863058e-001 + <_> + 6.61647394e-002 -1.68404490e-001 -4.65640664e-001 -3.64614844e-001 + -7.89388061e-001 7.09002197e-001 + <_> + 1.88160336e+000 -7.34207869e-001 5.97713590e-001 2.75218678e+000 + 2.94301063e-001 9.48754609e-001 + <_> + 1.77172804e+000 -1.06408679e+000 -1.04088449e+000 2.37243700e+000 + 1.67558655e-001 6.18097603e-001 + <_> + 2.06005946e-001 4.16996658e-001 -4.54517066e-001 6.10297740e-001 + 9.85866129e-001 1.03874004e+000 + <_> + 4.73449193e-003 6.08751595e-001 -9.50509548e-001 8.21541607e-001 + 9.21947896e-001 9.97964144e-001 + <_> + -1.56082898e-001 3.97792250e-001 -1.31109774e+000 5.56812942e-001 + 6.89225495e-002 7.69284964e-001 + <_> + 5.68094790e-001 -5.20022549e-002 5.73110059e-002 -7.54117191e-001 + -1.62123239e+000 -1.45043516e+000 + <_> + 6.75472856e-001 -9.97959912e-001 -1.29453743e+000 1.08270454e+000 + -2.72959232e+000 3.21010590e+000 + <_> + 8.35291386e-001 -1.05949569e+000 -1.93765354e+000 1.46717143e+000 + -1.97960436e+000 2.44184351e+000 + <_> + 6.17039204e-001 -1.52026749e+000 -3.85282457e-001 1.74561453e+000 + -2.32573223e+000 3.11653185e+000 + <_> + 8.82737517e-001 -3.00613356e+000 -8.73197734e-001 1.14241862e+000 + -1.58733559e+000 1.94252002e+000 + <_> + 5.18650949e-001 -2.87810826e+000 -1.52022457e+000 -2.21857186e-002 + -2.01536417e+000 1.73746896e+000 + <_> + 1.79036573e-001 -4.01963830e-001 6.35646105e-001 -1.07621498e-001 + 9.32413042e-001 -9.33295548e-001 + <_> + -7.59269693e-004 -4.33496088e-001 1.06904507e+000 -3.62278461e-001 + 3.51916820e-001 5.24391294e-001 + <_> + 1.04205656e+000 -1.35350943e+000 -2.10298479e-001 3.32597017e-001 + 2.47199678e+000 -1.43223131e+000 + <_> + 1.80219352e+000 -2.63785648e+000 -8.51261497e-001 -1.67174852e+000 + 1.13249505e+000 -1.07536209e+000 + <_> + -7.66730681e-002 -3.32438767e-001 -2.09117860e-001 1.20985597e-001 + -1.66823044e-002 1.91308737e+000 + <_> + -8.76605958e-002 -2.59649396e-001 -5.18325567e-001 4.42124419e-002 + -5.19489944e-001 1.64595878e+000 + <_> + 1.04904866e+000 -7.07318008e-001 2.14935756e+000 1.84770203e+000 + 5.61336100e-001 2.14551544e+000 + <_> + 9.77130353e-001 -5.93477964e-001 2.89047766e+000 7.78642952e-001 + -1.29228413e+000 7.71739066e-001 + <_> + 1.16641545e+000 -1.10856509e+000 2.83565545e+000 1.53220117e+000 + -2.07269812e+000 1.87810814e+000 + <_> + 1.28977537e+000 -2.81413150e+000 6.50583029e-001 2.88602024e-001 + -1.67053103e+000 1.12352848e+000 + <_> + 1.93004835e+000 3.27781200e+000 3.71346563e-001 9.32972431e-001 + -6.57227933e-001 6.37592673e-001 + <_> + 1.76223886e+000 -2.17702031e+000 -8.86603534e-001 1.00283110e+000 + -4.58277076e-001 2.12763262e+000 + <_> + 1.61490619e+000 1.65147811e-001 -1.31248128e+000 -9.17855263e-001 + -1.72039032e+000 -1.33414114e+000 + <_> + 1.82416856e+000 2.27902770e+000 -1.37388635e+000 -2.14362592e-001 + -3.15124846e+000 -1.62855017e+000 + <_> + 1.90208006e+000 2.98223853e+000 -1.09709549e+000 -5.38159311e-001 + -3.48177075e+000 -2.21466637e+000 + <_> + 3.98787051e-001 3.76854509e-001 -1.47861588e+000 1.13541096e-001 + -1.05838144e+000 -9.47938025e-001 + <_> + 1.42811823e+000 1.13907671e+000 -7.69554019e-001 1.17544222e+000 + -1.09930754e+000 2.17522144e+000 + <_> + 1.57245433e+000 -1.70116889e+000 -1.76263973e-002 1.25508860e-001 + -2.58409905e+000 2.61340761e+000 + <_> + 1.63488340e+000 -1.88238895e+000 1.31423867e+000 5.45912027e-001 + -3.19366980e+000 3.02020526e+000 + <_> + 1.62389588e+000 -1.59912336e+000 1.36651516e+000 3.67399484e-001 + -2.69103694e+000 1.82054281e+000 + <_> + 1.71179616e+000 -4.95856255e-001 4.96396460e-002 -1.14553916e+000 + -2.74437964e-001 -1.27444053e+000 + <_> + 2.14496315e-001 -3.73795931e-003 -6.04481459e-001 -3.21089745e-001 + 6.62785709e-001 -1.31702411e+000 + <_> + -5.96923530e-002 -6.09740987e-002 -4.58741456e-001 + -1.76303927e-002 4.50984865e-001 -1.03263474e+000 + <_> + 3.73815387e-001 -7.33423308e-002 -6.86136246e-001 -1.32808238e-001 + 2.89458215e-001 -3.31194520e-001 + <_> + 4.28253591e-001 -1.44758508e-001 -6.44279778e-001 -2.11181954e-001 + -5.60108721e-002 -1.69222996e-001 + <_> + 3.21374953e-001 -5.19546941e-002 2.43803322e-001 -2.03837290e-001 + 8.11782658e-001 -1.37498343e+000 + <_> + 4.68208224e-001 -3.90685871e-002 1.60435587e-001 -1.43112704e-001 + 8.42707753e-001 -1.77999258e+000 + <_> + -6.81827068e-002 -2.60208040e-001 3.19085956e-001 -8.29131976e-002 + 5.99666834e-002 -6.66961491e-001 + <_> + -1.18041980e+000 -1.59057066e-001 2.75445372e-001 4.63378243e-002 + -5.92198670e-001 -2.60695577e-001 + <_> + -3.77831101e-001 -1.63688123e-001 1.14660978e+000 7.55981863e-001 + 4.12263498e-002 1.75957546e-001 + <_> + -9.80646610e-001 -5.51646128e-002 3.16400588e-001 5.26510239e-001 + 2.77413487e-001 3.75790671e-002 + <_> + -1.50704885e+000 5.24205267e-002 2.03757286e-001 -4.67018932e-002 + 5.26884675e-001 -3.50243330e-001 + <_> + -1.74228179e+000 6.99037164e-002 -1.69017255e+000 1.03687716e+000 + -2.63656110e-001 3.64967108e-001 + <_> + -1.30727577e+000 5.09899557e-002 -5.38006008e-001 8.47202301e-001 + -2.78582692e-001 3.44078213e-001 + <_> + -1.97351921e+000 2.17021376e-001 -1.30545819e+000 4.39280778e-001 + 1.34603679e-001 3.11075114e-002 + <_> + -1.77424550e+000 5.99836931e-002 -5.29644191e-001 9.46030259e-001 + -1.36673641e+000 1.19276285e+000 + <_> + 4.13270593e-001 7.89363310e-002 9.57801104e-001 1.80423319e-001 + -8.07896912e-001 7.96302855e-001 + <_> + -2.53472328e-001 5.17766774e-001 -2.53912687e-001 1.05718875e+000 + -1.83910072e-001 -2.73244567e-002 + <_> + -4.39261347e-001 1.77957669e-001 -3.65749598e-001 5.20400345e-001 + 1.27892941e-001 -1.87015235e-001 + <_> + 1.49570033e-001 1.60458252e-001 -2.53367186e-001 -3.91772330e-001 + 6.58149660e-001 -1.32165432e+000 + <_> + 2.45461151e-001 -7.51469493e-001 -9.21755791e-001 -8.05514574e-001 + -1.07120477e-001 -1.13075161e+000 + <_> + 1.79470193e+000 5.06771135e+000 -1.02770314e-001 5.34697890e-001 + -1.44566107e+000 -1.45915997e+000 + <_> + 1.89508796e+000 3.50447774e+000 -7.56427586e-001 1.98324323e-001 + -4.60570717e+000 -2.36896491e+000 + <_> + 1.70330572e+000 2.86073637e+000 -2.10481071e+000 -2.60640960e-002 + -5.65912962e+000 -3.15972328e+000 + <_> + 1.42112613e+000 2.92784739e+000 -6.77336991e-001 -6.95642531e-002 + -4.54717207e+000 -2.78937984e+000 + <_> + 1.93254542e+000 5.26398516e+000 2.66332507e-001 4.67540920e-001 + -5.55753469e+000 -3.02141833e+000 + <_> + 1.79170537e+000 2.40443802e+000 9.86365139e-001 1.93216756e-001 + -3.92758060e+000 -2.36288333e+000 + <_> + 1.84814131e+000 2.28462124e+000 -3.15220326e-001 1.94892570e-001 + -7.13727832e-001 -2.36195374e+000 + <_> + 1.88160336e+000 1.51978922e+000 -1.15429211e+000 1.44933566e-001 + -2.57146978e+000 -2.48958325e+000 + <_> + 1.87511063e+000 4.61262512e+000 -7.45256543e-001 5.02823889e-001 + -2.62038207e+000 -2.04156661e+000 + <_> + 1.78870881e+000 4.79026794e+000 2.12549472e+000 4.12347794e-001 + -4.75663614e+000 -2.82520676e+000 + <_> + 1.73427057e+000 4.77026415e+000 -2.97718215e+000 1.50119945e-001 + -5.72727489e+000 -2.88187337e+000 + <_> + -9.16560590e-002 -7.36799138e-003 -6.17038667e-001 1.54245034e-001 + -4.21225578e-002 7.73886731e-003 + <_> + -1.28380239e+000 2.94981182e-001 -1.86047876e+000 1.37876022e+000 + -1.47873938e+000 1.23177969e+000 + <_> + 1.27095550e-001 -1.32266477e-001 5.36189489e-002 1.37385383e-001 + -1.13220476e-001 1.00484483e-001 + <_> + -4.94698405e-001 -1.23369229e+000 7.73709536e-001 -1.01269209e+000 + -2.62016594e-001 -4.01796490e-001 + <_> + -7.16787428e-002 -9.50298071e-001 1.43988717e+000 -1.41184563e-002 + -9.62807477e-001 1.25074923e+000 + <_> + 2.62441874e-001 -9.94331002e-001 1.82674658e+000 -9.46260467e-002 + -7.33606398e-001 8.79577100e-001 + <_> + 7.21579343e-002 -1.18755233e+000 5.06777287e-001 -6.65773153e-001 + -8.38274181e-001 5.76273978e-001 + <_> + 1.12745965e+000 6.86509311e-002 1.72513172e-001 -2.12060049e-001 + -6.34051859e-002 -1.57060698e-001 + <_> + 1.96017295e-001 -4.77531850e-002 -3.03906965e+000 1.35963643e+000 + -9.72713649e-001 9.01748240e-001 + <_> + 1.61556423e-001 -5.76668918e-001 1.92080581e+000 5.02525195e-002 + 1.39473724e+000 -1.56916186e-001 + <_> + 1.68832278e+000 4.63220501e+000 -2.14325219e-001 8.59878600e-001 + -1.84045470e+000 -1.00809133e+000 + <_> + 1.62189817e+000 2.97533751e+000 -1.67755997e+000 7.20404267e-001 + -2.09242368e+000 -1.39365757e+000 + <_> + 1.56396401e+000 1.59643662e+000 -1.39952254e+000 8.78366351e-001 + -1.08797765e+000 -5.76965094e-001 + <_> + 1.64437270e+000 7.34553337e-001 2.55056053e-001 -2.29045674e-001 + -5.56295775e-002 1.63899511e-001 + <_> + 1.53399801e+000 2.64790082e+000 9.93396103e-001 -2.97941715e-001 + -6.87742710e-001 -7.14593172e-001 + <_> + 1.74425924e+000 -2.83362679e-002 1.71597445e+000 -6.52626336e-001 + 9.07557458e-002 -1.80053389e+000 + <_> + 1.76373720e+000 -2.00364023e-001 2.71338034e+000 7.18273044e-001 + -3.07576478e-001 -1.11430347e+000 + <_> + 1.17740297e+000 1.36756265e+000 9.17517543e-002 3.43465567e-001 + -1.19310999e+000 4.74003494e-001 + <_> + 1.28977537e+000 3.13297063e-001 -1.89237162e-001 -7.74947882e-001 + -1.52932405e+000 -2.25894451e+000 + <_> + 1.74725592e+000 4.55157489e-001 1.44167125e+000 -6.55090630e-001 + -4.83960733e-002 -8.99507165e-001 + <_> + -4.13290828e-001 -1.96161885e-002 -1.48558092e+000 + -1.48735449e-001 7.30227768e-001 -8.11784506e-001 + <_> + -2.76446223e-001 -8.08594450e-002 -1.44059634e+000 1.20355688e-001 + -3.69953781e-001 -5.43237090e-001 + <_> + 2.82918602e-001 -1.28311068e-001 1.17386675e+000 -2.98987895e-001 + 6.49912655e-001 -6.37817442e-001 + <_> + 4.64712203e-001 -2.59337991e-001 1.09149253e+000 1.30800130e-002 + -1.18091844e-001 1.76938534e-001 + <_> + 1.01159120e+000 -3.11275333e-001 7.96515465e-001 -4.12052214e-001 + -2.33414575e-001 -6.25533387e-002 + <_> + 1.08201122e+000 -1.52440441e+000 -8.71998668e-001 9.61517811e-001 + -2.06409240e+000 -1.30533779e+000 + <_> + 1.82766461e+000 1.27616143e+000 -6.14253163e-001 1.11447468e-001 + -1.18312664e-001 3.27534586e-001 + <_> + 1.51252246e+000 -1.79781604e+000 -2.51235199e+000 5.58020413e-001 + 4.12481308e-001 -1.20091486e+000 + <_> + 1.69681311e+000 -1.32277286e+000 -1.54373682e+000 -3.66252542e-001 + -2.46403837e+000 -2.25870872e+000 + <_> + 6.79468334e-001 -3.71518701e-001 -1.02188933e+000 -5.05667806e-001 + 1.75105774e+000 -1.22670019e+000 + <_> + 3.66323918e-001 -7.42519915e-001 6.34828806e-002 -5.26719153e-001 + 5.25032163e-001 -5.89017510e-001 + <_> + 1.39116025e+000 -8.99889052e-001 -2.55940795e+000 8.36943746e-001 + -1.57363892e+000 1.27466679e+000 + <_> + 1.08350956e+000 -3.02759480e+000 -1.10250843e+000 5.26792884e-001 + -1.48173976e+000 1.70079879e-002 + <_> + 1.70480406e+000 -1.50786185e+000 -1.89855623e+000 5.86002350e-001 + -2.08217049e+000 -5.39100230e-001 + <_> + 1.32074022e+000 -1.35049355e+000 -2.88312054e+000 5.81827819e-001 + -3.90659124e-001 -1.32412839e+000 + <_> + 1.54248834e+000 -2.34298563e+000 -2.43790221e+000 4.85783279e-001 + -7.60004938e-001 -6.06291056e-001 + <_> + 1.01558661e+000 -2.07823381e-001 8.02982450e-001 -2.07008287e-001 + -1.10703838e+000 6.42863214e-001 + <_> + 1.06253338e+000 -6.82586074e-001 -3.84707958e-001 -7.27678716e-001 + 2.27492586e-001 -3.23065311e-001 + <_> + 1.59043396e+000 -9.97874975e-001 1.77989101e+000 -3.55845779e-001 + -3.02616507e-001 -7.67213181e-002 + <_> + 4.21919599e-002 -8.05002034e-001 -2.53338218e-001 1.36251140e+000 + -2.99373984e+000 2.64606953e+000 + <_> + -1.17492604e+000 8.20156485e-002 5.74678421e-001 7.63900280e-001 + -1.04137456e+000 1.26237941e+000 + <_> + 1.42612052e+000 -6.44904613e-001 5.16053557e-001 -5.00386775e-001 + -1.02185476e+000 -2.57637715e+000 + <_> + 1.40014994e+000 -1.29628384e+000 3.05900502e+000 9.87298071e-001 + -3.39066058e-001 2.40624472e-002 + <_> + 1.68083131e+000 -2.75033998e+000 1.96151853e+000 1.28719294e+000 + -4.28842485e-001 -5.40366657e-002 + <_> + 1.09599543e+000 -3.18617448e-002 -2.19329095e+000 -4.69480336e-001 + 1.40036929e+000 -7.71319330e-001 + <_> + 1.47057009e+000 -4.91214767e-002 -2.62593865e+000 6.71969831e-001 + -5.76427460e-001 5.25860131e-001 + <_> + 1.90907216e+000 3.42026681e-001 2.88737845e+000 3.51039797e-001 + 1.09543872e+000 -1.93393540e+000 + <_> + 1.77921963e+000 -2.80471206e-001 9.55479383e-001 -8.91628981e-001 + 4.71270531e-001 -8.34805787e-001 + <_> + 1.81218219e+000 -4.29547846e-001 9.74271715e-001 -6.27578080e-001 + -3.85992646e-001 8.30548257e-002 + <_> + -5.76946214e-002 5.54192781e-001 -2.89288950e+000 8.63562644e-001 + -2.18161035e+000 -4.67817634e-001 + <_> + -1.75560787e-001 3.25461447e-001 -2.00258207e+000 7.27645636e-001 + -2.53874332e-001 3.26966703e-001 + <_> + -3.66843581e-001 7.58237988e-002 2.64640629e-001 5.70339084e-001 + 1.01340926e+000 -5.56338727e-001 + <_> + 1.81118321e+000 1.01734245e+000 9.64410424e-001 -8.52641940e-001 + 1.09150898e+000 -1.68255591e+000 + <_> + 1.87660897e+000 1.17318368e+000 8.65855455e-001 5.22754751e-002 + 8.32470804e-002 -6.19002461e-001 + <_> + 1.66085398e+000 2.86758602e-001 4.70009595e-001 -4.02277708e-001 + -7.51169622e-001 8.44150066e-001 + <_> + 1.25281727e+000 6.94194913e-001 -1.33991730e+000 2.75185913e-001 + -5.86295843e-001 7.10369289e-001 + <_> + 1.48755074e+000 1.09875590e-001 5.70469856e-001 -5.55140018e-001 + 4.48777601e-002 -2.41731912e-001 + <_> + 1.72777796e+000 -4.62769985e-001 1.91407526e+000 1.56708717e-001 + -4.97893214e-001 1.53565869e-001 + <_> + 1.84414589e+000 7.07947314e-001 -4.97507080e-002 -3.21357518e-001 + 1.91673684e+000 -1.52722108e+000 + <_> + 1.85862935e+000 8.17894161e-001 7.60295466e-002 -3.03455800e-001 + 1.20855725e+000 -7.50456393e-001 + <_> + 1.28028607e+000 7.42195010e-001 6.09438062e-001 -2.50576377e-001 + 1.76763260e+000 -1.54879057e+000 + <_> + 1.42811823e+000 -8.85309279e-001 2.00682449e+000 -2.24705592e-001 + 6.51930213e-001 -9.74144757e-001 + <_> + 1.43461084e+000 -9.15546060e-001 1.25033259e+000 -3.96091402e-001 + -9.64233950e-002 -1.37028351e-001 + <_> + -9.31543559e-002 -4.89627033e-001 1.01799929e+000 -7.35438406e-001 + 8.24492574e-001 -8.09348762e-001 + + <_> + 296 + 1.7408360072295771e-002 + + 2.6623686228416282e-001 1. 3.8802671672077355e-001 1. + 5.7741230625098150e-001 7.5313761133996815e-001 1. + 3.5487439992249209e-001 1.8514773498670239e-001 + 2.2184250354320803e-001 3.6802164841733020e-001 + 1.1738183528952924e-001 2.0908492131305717e-001 + 8.3976431612477054e-001 4.7107412337012688e-001 + 9.2329493802702042e-002 3.9618391673085812e-001 + 3.8049261008669188e-001 1. 1. 5.1670674490463819e-001 1. 1. + 9.3989029169004798e-002 1.7978353989791560e-001 1. 1. 1. + 6.8043916763305423e-001 6.4958276735264187e-001 + 7.4838089471320823e-001 7.5367294423089892e-001 + 4.0451340084097775e-001 1.6237088493051438e-001 + 3.2457782413177244e-001 6.7534426258314162e-001 + 3.4591764522217766e-001 2.9440084622093016e-001 + 1.9236727127712927e-001 5.4803098910072634e-001 + 1.9608980653617608e-001 7.7940972343974912e-001 + 4.5561718452869576e-001 1. 8.4644973822545866e-001 + 1.9558188503638765e-001 2.1366851296432818e-001 1. + 1.3248499041003894e-001 2.4000254501594712e-001 1. + 3.2984176882668642e-001 7.4797581423034443e-001 1. 1. + 3.1920249495840680e-001 1. 1. 4.7896956818552960e-001 + 2.7808763327401909e-001 2.8310820314564539e-001 + 1.9733938042840979e-001 5.3152052934981675e-001 1. 1. 1. 1. 1. + 1. 1. 1. 1. 6.3528248490054395e-001 1. 1. 1. 1. + 7.0218966477831679e-001 1. 1. 1. 1. 1. 1. 1. 1. + 6.3419681717842058e-001 1. 1. 1. 1. 4.0206078562366403e-002 + 8.9731502920280204e-001 1. 5.0913247412777596e-001 + 5.7143449674739244e-001 3.5958426811684518e-001 + 4.7684515715207687e-001 7.4680497722247788e-002 + 1.8904242212433947e-001 2.7854512195834463e-001 + 8.0503632728492480e-001 9.9404798739075673e-001 + 7.9245000645260377e-001 1. 9.0270193235984852e-001 1. 1. 1. 1. + 1. 4.4455496038050746e-001 6.7006685766960827e-001 + 3.1492322903922820e-001 2.4913821688352630e-001 + 1.1514328326761398e-002 3.7697928467931285e-001 + 6.7544590352288303e-001 1. 1. 2.7987846503794123e-001 1. + 2.9768497776631009e-001 1. 1. 2.8303553217952365e-001 + 7.2376729608443435e-001 3.5381652198250527e-002 1. + 2.9649897680881820e-001 1. 9.8223370377929708e-001 1. + 8.4670958119941828e-003 4.4468236249095339e-001 + 7.4676256444887701e-001 3.8319928110674095e-001 + 2.7237283865630885e-001 1. 9.3200977525818141e-001 1. 1. + 1.1721831352484026e-001 2.1363447676841138e-001 -1. -1. + -2.0426625715538499e-001 -4.8394786937207107e-001 -1. -1. -1. + -1. -1. -8.7219787554096950e-001 -8.1965741630879230e-001 + -1.8645490433375067e-001 -1. -6.8819578553989824e-001 + -1.8046112798709635e-001 -2.9374851612902692e-001 + -1.7286092601222608e-001 -2.4134788557335757e-001 + -3.7610521407956593e-001 -3.9930577532853789e-001 -1. -1. -1. + -1. -1. -5.1634557850650131e-001 -3.4174135925904547e-001 -1. + -1. -1. -1. -4.3680803660779854e-001 -1.2819561773783747e-001 + -7.2622255917135239e-002 -2.1829801383959799e-001 + -5.0203953980317184e-001 -4.3240872916048495e-001 -1. + -3.8554824364012213e-001 -8.0406365067045926e-001 -1. + -7.9088869608027201e-001 -4.9365387323984328e-001 + -1.3591669302235451e-001 -4.7097046814685378e-001 + -1.8298749787040630e-001 -5.7408743292217290e-001 + -3.7709659637734827e-001 -4.8614132343129185e-001 + -4.2207533925025809e-001 -1.6750257614080036e-001 + -2.3961605729186042e-001 -5.0739236659697418e-001 + -7.0843827167921841e-002 -7.2917369932476983e-001 + -5.0036725704592035e-002 -1. -1. -9.3568650382488283e-001 -1. + -5.8928389592416419e-001 -1. -1. -1. -1. -1. -1. -1. + -6.2737702244998128e-001 -1. -1. -1. -1. + -8.0479635859577736e-003 -1. -1. -9.2928872111776473e-001 + -6.2217352654826996e-001 -7.9698287131166862e-002 + -6.7434911600051262e-001 -2.2336698578755293e-001 + -7.3720811459419000e-001 -7.0901086437774763e-001 + -5.6407987954533434e-001 -1.3126787934360357e-001 + -4.0575709152591544e-001 -7.6432059179595202e-001 + -8.0908705219035282e-001 -1. -2.2591057322033067e-004 + -9.9120021639764455e-001 -1. -1. -4.9112854435022162e-002 + -4.1106215547375291e-001 -1. -1.7630988929249700e-001 -1. + -6.4602048456419037e-002 -3.5186759031666787e-001 + -3.2092897005702337e-001 -1. -7.4286485940490754e-001 -1. + -1.5929459492887613e-001 -5.4649914949691825e-001 + -6.9886406827842285e-001 -1. -1. -1.2483738323419406e-001 -1. + -1. -1. -8.6101382787987193e-002 -2.0738609336497915e-001 + -1.6762476988944758e-001 -5.2676129731953969e-001 + -9.8406609892648333e-001 -9.8946727803476495e-003 + -3.8626031148355688e-001 -3.7206949760355279e-001 + -8.0416348825605166e-002 -2.1655333321134554e-001 + -3.0241999923999741e-001 -8.3776970134309889e-002 -1. -1. + -3.3446941533486785e-001 -1. -3.6808265864048467e-001 + -1.1062198802987413e-002 -5.6331528410391762e-001 + -6.2872153528085184e-001 -1.3619663663591370e-002 + -5.1677993676622436e-001 -1. -1. -9.4316370108267722e-001 + -4.5160869291083550e-001 -1. -1. -1.9014702585896409e-001 -1. + -5.4327936522519167e-002 -1. -1. -4.8619304611216951e-001 -1. + -1. -1.1073612459897134e-001 -4.0344917024365795e-002 -1. + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 + 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 + 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 + 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 + 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 + 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 + 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 + 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 + 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 + 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 + 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 + 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 + 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 + 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 + 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 + 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 + 283 284 285 286 287 288 289 290 291 292 293 294 295
+
diff --git a/acii/acii-client/trainedClassifiers/SVM_UD_N00049.xml b/acii/acii-client/trainedClassifiers/SVM_UD_N00049.xml new file mode 100755 index 0000000..edb2a7a --- /dev/null +++ b/acii/acii-client/trainedClassifiers/SVM_UD_N00049.xml @@ -0,0 +1,1869 @@ + + + + C_SVC + RBF + 1.9530000000000000e-001 + 1. + 2.2204460492503131e-016 + 100000 + 6 + 6 + 2 + + 1 + 2 +
i
+ + -1 1
+ 577 + + <_> + -8.60310122e-002 -2.12246045e-001 2.43087602e+000 -9.53558311e-002 + -5.51886499e-001 2.45125026e-001 + <_> + -2.00194740e+000 -1.59038454e-001 -9.19705331e-002 8.77058059e-002 + -1.39369264e-001 6.99959174e-002 + <_> + 1.15105882e-002 -3.28217670e-002 -6.85558259e-001 -3.35947424e-002 + -8.78290951e-001 7.01520503e-001 + <_> + 3.16882282e-001 -3.42670799e-004 -2.19252944e-001 -2.64927328e-001 + 6.24103621e-002 -2.28859663e-001 + <_> + -1.70378053e+000 -5.40995710e-002 -5.23639321e-001 + -5.78096211e-001 -7.77476370e-001 2.02800304e-001 + <_> + -1.75365973e+000 -1.35704398e-001 -6.41503096e-001 + -3.62078607e-001 -6.18123591e-001 1.22136422e-001 + <_> + -1.70932269e+000 -1.23961553e-001 -4.57532734e-001 + -2.79446721e-001 -5.99829733e-001 7.59545118e-002 + <_> + -1.71486485e+000 -5.52349873e-002 -1.28999078e+000 + -3.43690246e-001 -3.55536014e-001 1.83787663e-002 + <_> + -1.62286532e+000 -3.99387553e-002 -1.14352822e+000 + -3.96213382e-001 -3.83624256e-001 2.92129582e-003 + <_> + 1.11716676e+000 -3.65094721e-001 -7.18102038e-001 -1.12592077e+000 + 1.66368961e-001 -3.95957112e-001 + <_> + 5.50760448e-001 2.93606132e-001 -6.47550821e-002 -6.87368035e-001 + -5.82696319e-001 8.96362662e-002 + <_> + 6.44976795e-001 3.02164257e-001 1.20060459e-001 -6.47663176e-001 + -5.24907231e-001 4.16012257e-002 + <_> + 6.74350083e-001 3.09666246e-001 1.02223165e-001 -7.33823419e-001 + -6.13955677e-001 6.78405166e-002 + <_> + 7.14253485e-001 2.79268354e-001 2.21079752e-001 -7.00452030e-001 + -5.18613100e-001 -2.19098553e-002 + <_> + 8.01265001e-001 1.04178503e-001 3.66358995e-001 -1.57113147e+000 + 4.52512980e-001 -5.14438510e-001 + <_> + 3.75628948e-001 5.62114865e-002 -6.40805483e-001 1.26747247e-002 + 6.26461923e-001 -8.51706564e-001 + <_> + 7.60807455e-001 2.34883856e-002 -2.80957311e-001 -4.75506127e-001 + 4.25350219e-001 -7.80458093e-001 + <_> + 4.61532056e-001 2.53271908e-001 -4.04968411e-001 -7.09707379e-001 + 2.13903233e-001 -4.38346148e-001 + <_> + 2.84183681e-001 2.77051479e-001 -5.49861968e-001 -8.65387738e-001 + 1.59084707e-001 -3.24889123e-001 + <_> + 1.62371802e+000 -1.93873391e-001 2.19260097e+000 7.34018348e-003 + -4.06037271e-001 -4.80847619e-003 + <_> + 1.52839327e+000 -1.67812794e-001 2.05016923e+000 4.31123713e-004 + -6.64206073e-002 -1.34899780e-001 + <_> + -1.34247601e-001 -2.05883130e-001 -3.83317024e-001 + -2.71988809e-001 5.35225987e-001 -5.99899650e-001 + <_> + 8.79920647e-002 -2.30572686e-001 -1.02244318e+000 -9.11120653e-001 + 2.99998075e-001 -2.61545718e-001 + <_> + 2.37032883e-002 -2.83043981e-001 -8.64835978e-001 -9.81169879e-001 + 1.45516813e-001 -1.13746963e-001 + <_> + -4.54028845e-001 -2.30004162e-001 -6.18052110e-002 + -3.56367439e-001 9.49749112e-001 -5.02237856e-001 + <_> + -6.66292608e-001 -2.36785591e-001 -2.66788900e-001 + -3.80977154e-001 9.63988066e-001 -4.27015036e-001 + <_> + 3.62882018e-001 -1.38197362e-001 -1.34051979e-001 -7.63114452e-001 + 3.78767341e-001 -5.53072274e-001 + <_> + 2.57027209e-001 -3.67807150e-001 -2.67015100e-001 -2.49822646e-001 + 6.96747243e-001 -2.70618558e-001 + <_> + 4.44905639e-001 1.14674234e+000 5.21635003e-002 -4.29937661e-001 + -8.57551098e-001 5.49654186e-001 + <_> + -7.89882302e-001 2.33343691e-001 -8.60590935e-001 -1.22403145e+000 + -8.39706123e-001 5.66817164e-001 + <_> + -1.03983259e+000 2.34987140e-001 -1.29836667e+000 -1.20780277e+000 + -7.44175911e-001 5.12359679e-001 + <_> + -1.00935090e+000 1.36085010e+000 -2.08347201e+000 2.95258880e+000 + -1.51964819e+000 1.54189146e+000 + <_> + -7.99346566e-002 4.07030404e-001 3.53845268e-001 -4.80174065e-001 + 3.01079082e+000 -3.39155006e+000 + <_> + 1.11051619e+000 6.28069818e-001 1.22161841e+000 -2.79475600e-001 + 1.85466695e+000 -1.76164496e+000 + <_> + 1.10552835e+000 6.38598382e-001 1.16643751e+000 -2.73156345e-001 + 1.89062011e+000 -1.77125323e+000 + <_> + 9.99119282e-001 6.41992331e-001 9.14415419e-001 -2.73965299e-001 + 1.91951573e+000 -1.81141114e+000 + <_> + 1.11827517e+000 6.41765893e-001 1.16863501e+000 -2.35791251e-001 + 1.86417949e+000 -1.78059316e+000 + <_> + -1.95253752e-002 3.67480904e-001 -1.29285264e+000 -9.03056443e-001 + 5.09668112e-001 -2.70072609e-001 + <_> + -1.29813880e-001 -2.32706398e-001 3.09430569e-001 4.32531625e-001 + -1.77697495e-001 1.46468207e-001 + <_> + -9.21273604e-002 -1.41626159e-002 3.43121946e-001 -2.54092991e-001 + 8.42616618e-001 -7.68136203e-001 + <_> + -1.77527404e+000 5.93382835e-001 -2.76293921e+000 -2.45336637e-001 + 8.83558393e-001 -7.76340246e-001 + <_> + -1.20946467e-001 -2.06636816e-001 -2.92780608e-001 2.60819346e-001 + 2.59325910e+000 -1.91044354e+000 + <_> + -2.38993973e-001 -3.14442843e-001 -2.86209464e-001 + -3.05759370e-001 2.30627298e+000 -1.68087292e+000 + <_> + -1.61958277e-001 -2.68364072e-001 -3.21791202e-001 2.63185173e-001 + 2.72192430e+000 -1.78988671e+000 + <_> + -2.85547912e-001 -2.88535088e-001 -1.50534585e-001 1.25997615e+000 + 1.61084390e+000 -1.15373826e+000 + <_> + -2.24030197e-001 -2.72860795e-001 -7.26430237e-001 1.03227878e+000 + 1.02412558e+000 -5.55619359e-001 + <_> + -1.96319520e-001 -2.29267865e-001 2.49218624e-002 6.52302802e-001 + 9.28064108e-001 -5.14010906e-001 + <_> + -2.83331066e-001 -3.20579290e-001 -9.07967761e-002 1.28544939e+000 + 1.14494598e+000 -5.61248422e-001 + <_> + 2.89171606e-001 -2.89158374e-001 -9.92404044e-001 2.60454369e+000 + 4.09156203e-001 -4.42749448e-002 + <_> + 1.88304737e-001 -2.38927469e-001 -1.13917601e+000 2.43926072e+000 + 5.42906880e-001 -1.69756651e-001 + <_> + 2.03268498e-001 -2.62622416e-001 -9.21859622e-001 2.51348305e+000 + 3.74813199e-001 -1.92872807e-002 + <_> + -3.23234439e-001 -2.51924664e-001 1.39376268e-001 1.43286943e+000 + 1.31983328e+000 -9.29049373e-001 + <_> + -1.09747088e+000 6.63504362e-001 1.52018821e+000 -4.68605280e+000 + -1.16305029e+000 1.73931971e-001 + <_> + -6.06991768e-001 -2.27748662e-001 5.04893243e-001 3.63395333e-001 + -3.84623498e-001 -4.86203004e-004 + <_> + -8.46412063e-001 -2.21958771e-001 4.28275257e-001 -2.18428358e-001 + -3.26688558e-001 1.80445820e-001 + <_> + -9.81640160e-001 -7.10506961e-002 -1.60688892e-001 + -3.47883463e-001 -7.11192012e-001 2.01183811e-001 + <_> + -9.87779200e-002 -4.08080965e-002 1.00118542e+000 2.91206278e-002 + -1.31286785e-001 -1.34848664e-002 + <_> + -9.82237086e-002 -4.70469557e-002 8.25667024e-001 2.30209664e-001 + -3.04689586e-001 8.05952251e-002 + <_> + -2.83331066e-001 -3.21942475e-003 6.36289775e-001 3.81140150e-002 + -3.67779851e-001 4.00528647e-002 + <_> + 1.24685276e+000 -1.63402736e-001 5.70885539e-001 5.26354790e-001 + -7.47516930e-001 4.80604321e-001 + <_> + 1.18810618e+000 -1.54340044e-001 7.01953530e-001 5.97904921e-001 + -6.55621767e-001 4.19676125e-001 + <_> + 1.20196152e+000 -1.23514622e-001 6.82535350e-001 6.30552292e-001 + -7.26708651e-001 4.11014587e-001 + <_> + -2.23475993e-001 2.74860468e-002 -6.62118852e-001 -3.34542066e-001 + 9.91132483e-002 -6.84385359e-001 + <_> + 6.40543103e-001 8.68874695e-003 2.42916867e-001 -3.13568145e-001 + -1.85410678e-001 5.13487518e-001 + <_> + 6.93747580e-001 3.08716521e-002 2.82153219e-001 -2.71510422e-001 + -2.75501102e-001 4.87942398e-001 + <_> + 6.47150949e-002 -4.78274003e-002 -6.88393831e-001 -2.51699477e-001 + -2.49076948e-001 3.34233761e-001 + <_> + 1.75950038e+000 -4.55748528e-001 -2.86709797e-002 -1.06652153e+000 + -6.62199259e-002 8.20270956e-001 + <_> + 1.69244051e+000 -5.66436827e-001 -2.42851883e-001 -8.95615995e-001 + -6.52120039e-002 1.05649686e+000 + <_> + -3.11595947e-001 3.31776328e-002 -3.01825434e-001 -7.32492805e-001 + 1.73160791e-001 -2.18555983e-002 + <_> + -3.68125737e-001 -8.64674971e-002 -3.37802380e-001 + -5.51712155e-001 1.69364899e-001 4.67187837e-002 + <_> + -2.37885535e-001 -8.86232778e-002 -2.27816835e-001 + -5.73917210e-001 3.07853907e-001 3.44327688e-002 + <_> + 7.02060819e-001 -4.11202878e-001 3.15094620e-001 -7.04822659e-001 + -3.30412745e-001 1.73374936e-001 + <_> + -3.39349285e-002 6.98254287e-001 1.90417349e+000 1.48662758e+000 + 6.35414541e-001 -4.47694331e-001 + <_> + -9.26815718e-002 6.37882054e-001 2.01260662e+000 1.13441920e+000 + 9.75007296e-001 -7.79905677e-001 + <_> + -7.66051114e-001 8.26879665e-002 -1.21328741e-001 1.07544112e+000 + 5.72237790e-001 1.39087155e-001 + <_> + 1.32444274e+000 2.58811444e-001 -7.06521630e-001 6.20687425e-001 + 1.92484125e-001 3.69036198e-001 + <_> + 1.14432323e+000 2.22454444e-001 -5.91083884e-001 2.36430243e-001 + -3.30661237e-001 3.69425923e-001 + <_> + -3.23234439e-001 7.23026693e-002 5.61347902e-001 -2.01524809e-001 + -3.09209377e-001 6.01161495e-002 + <_> + -4.54583079e-001 -7.99337476e-002 2.16101054e-002 -1.55655131e-001 + -3.23742807e-001 4.64208722e-001 + <_> + -4.31860298e-001 -3.29778977e-002 2.50837598e-002 7.25994468e-001 + -4.69487101e-001 6.03159010e-001 + <_> + -4.86173242e-001 -4.09830287e-002 -1.13622295e-002 7.19735742e-001 + -4.59486872e-001 6.11391902e-001 + <_> + -5.57666779e-001 2.22062413e-002 -2.27295429e-001 -3.54794919e-001 + -3.50124300e-001 2.57447779e-001 + <_> + -4.15788114e-001 5.95997497e-002 2.45433077e-002 1.54926255e-001 + -2.34230861e-001 2.21722588e-001 + <_> + -5.88702738e-001 4.43174168e-002 2.65381089e-003 1.59880459e-001 + -2.08007455e-001 2.06443742e-001 + <_> + -8.99105072e-002 4.91097616e-003 4.18582857e-001 1.04482067e+000 + 4.61045057e-001 -1.96153998e-001 + <_> + -1.25542176e+000 -5.07673025e-002 -1.54977247e-001 2.93655753e-001 + -6.82314217e-001 4.57238823e-001 + <_> + -4.33565602e-002 -1.14477687e-001 1.46623111e+000 -1.05478609e+000 + -8.78787413e-002 1.08781867e-001 + <_> + -5.49950451e-002 -1.70498878e-001 1.58076179e+000 -4.67874318e-001 + 3.62105757e-001 -2.07300052e-001 + <_> + -1.19838037e-001 -1.39475629e-001 1.44056797e+000 -6.25904500e-001 + 3.95228833e-001 -2.41653875e-001 + <_> + -6.27540350e-002 -1.39157236e-001 1.43865383e+000 -4.37544644e-001 + 1.26424104e-001 -7.45050609e-002 + <_> + -1.33412004e+000 1.44315791e+000 2.23887277e+000 -3.00598741e+000 + 2.88184452e+000 -2.70961070e+000 + <_> + -1.40284252e+000 1.04512453e+000 2.16140246e+000 6.06420159e-001 + -2.73018622e+000 1.89200974e+000 + <_> + -1.37679446e+000 8.29976797e-001 1.77929568e+000 8.15164626e-001 + -2.66582775e+000 2.02035093e+000 + <_> + -2.21919918e+000 -1.51304770e+000 -7.51312435e-001 5.46341419e-001 + 1.33410144e+000 -1.00188112e+000 + <_> + -2.00305581e+000 -1.06524217e+000 -1.80205846e+000 7.61500120e-001 + 1.27988708e+000 -1.07634127e+000 + <_> + 3.19738360e-003 5.48777103e-001 -3.03111106e-001 1.81902957e+000 + -3.99185687e-001 1.16338648e-001 + <_> + 6.52693063e-002 6.58671677e-001 -1.03922382e-001 2.71095300e+000 + -3.18509936e-001 2.77340800e-001 + <_> + -9.94387090e-001 6.72059000e-001 1.02742188e-001 1.13605630e+000 + -1.66684818e+000 1.98637795e+000 + <_> + 1.25124380e-001 3.86781126e-001 2.90659964e-001 7.34668136e-001 + 4.26367193e-001 -1.25871729e-002 + <_> + -2.08785057e+000 3.89321923e-001 -9.72182583e-003 -9.34049487e-001 + 1.44369054e+000 -1.85882318e+000 + <_> + -1.60014260e+000 3.09110308e+000 2.50297487e-001 2.48148322e-001 + 1.98514986e+000 -1.60087252e+000 + <_> + -1.00602555e+000 2.74054384e+000 2.35525799e+000 -1.42357552e+000 + 3.73768854e+000 -3.30730104e+000 + <_> + -1.33139163e-001 2.18127161e-001 1.23410314e-001 5.64905465e-001 + -2.99415141e-001 2.62709707e-001 + <_> + -9.32357833e-002 2.20246360e-001 4.34417844e-001 5.79335153e-001 + -2.06331074e-001 2.53971785e-001 + <_> + -3.00554335e-002 1.91620633e-001 3.47628862e-001 2.62661248e-001 + 1.02352381e-001 1.88942268e-001 + <_> + -2.43427679e-001 5.62855363e-001 5.84820628e-001 1.18053293e+000 + -3.90620023e-001 5.08499801e-001 + <_> + -7.77178034e-002 -2.60076135e-001 2.90990919e-001 -1.96469891e+000 + 1.34382889e-001 -2.50928432e-001 + <_> + -6.10913970e-002 -1.38936117e-001 2.94250280e-001 -1.73963153e+000 + 2.68644720e-001 -3.90551031e-001 + <_> + -4.11397070e-002 -1.55573756e-001 4.19692308e-001 -1.77759755e+000 + 2.65199959e-001 -3.48015666e-001 + <_> + -2.25692838e-001 -1.85007095e-001 -3.32233578e-001 + -1.06802416e+000 -1.00594747e+000 6.79958761e-001 + <_> + -1.50319785e-001 -8.49021152e-002 -2.52096713e-001 + -1.43526244e+000 -6.65489554e-001 3.52804333e-001 + <_> + -2.15717003e-001 -1.63264588e-001 -2.22800404e-001 + -1.59163976e+000 -8.86196554e-001 5.11832654e-001 + <_> + 1.73895180e-001 1.98356286e-001 -3.52692187e-001 6.31457806e-001 + 3.17005277e-001 -4.86361027e-001 + <_> + -9.15731490e-002 3.57470185e-001 2.00843072e+000 -9.17414427e-001 + -1.71582878e+000 1.11209714e+000 + <_> + 1.20648015e-002 5.73383197e-002 -1.90190300e-002 -1.77857387e+000 + 9.59414423e-001 -9.87166524e-001 + <_> + 4.80886884e-002 3.70144621e-002 3.35262381e-002 -1.75927782e+000 + 9.45833147e-001 -9.57269847e-001 + <_> + -3.28265019e-002 2.03698762e-002 -8.36113840e-002 -1.72527409e+000 + 9.71666873e-001 -1.04821682e+000 + <_> + -6.82111655e-004 -2.97854114e-002 7.26530626e-002 -1.97052372e+000 + 4.11898106e-001 -5.45790970e-001 + <_> + -5.27781919e-002 -3.78988497e-002 1.09010609e-002 -5.04935205e-001 + -2.48398706e-001 -1.65224145e-003 + <_> + -3.89228500e-002 -4.72891219e-002 5.19158952e-002 -4.95991737e-001 + -2.67882884e-001 2.38330532e-002 + <_> + 2.86912099e-002 -5.01513667e-002 1.85755149e-001 -4.98372883e-001 + -2.39167094e-001 -7.88617805e-002 + <_> + 9.61986959e-001 1.86674342e-001 -1.34082186e+000 2.40039244e-001 + -6.39929116e-001 1.31648973e-001 + <_> + 1.89967379e-001 3.03499550e-001 -1.03319037e+000 2.57151455e-001 + -4.96693999e-001 6.06184974e-002 + <_> + 9.51968431e-002 3.07551950e-001 -1.50380981e+000 3.18720669e-001 + -6.18341148e-001 1.17305957e-001 + <_> + 8.63891184e-001 7.21771941e-002 4.33389336e-001 -1.00464487e+000 + 4.60148960e-001 -3.52096111e-001 + <_> + 9.80276048e-001 2.62255631e-002 8.46204221e-001 -9.52616811e-001 + 4.00175422e-001 -3.20888400e-001 + <_> + 2.43726090e-001 4.66450483e-001 -1.89028776e+000 -6.33125901e-001 + 8.60704005e-001 -7.55758286e-001 + <_> + -2.80005783e-001 -4.50340891e-003 -1.67892110e+000 + -5.60912609e-001 4.62015003e-001 -4.27930802e-001 + <_> + -6.82111655e-004 -3.19724567e-002 -8.53448391e-001 + -7.33090639e-001 3.06993961e-001 -3.20908785e-001 + <_> + 2.94159532e-001 -1.42342776e-001 -1.60698617e+000 -6.07044816e-001 + -7.23879114e-002 -2.53392398e-001 + <_> + -9.04647186e-002 2.11918056e-002 -2.45280552e+000 2.26355046e-001 + -3.17115217e-001 3.18811774e-001 + <_> + 2.20406465e-002 -1.30267916e-002 -2.32695389e+000 2.93453038e-001 + -3.64288807e-001 3.32428694e-001 + <_> + -1.51922739e+000 3.51301336e+000 4.41291362e-001 1.98109075e-001 + 3.63225430e-001 -1.80087417e-001 + <_> + -1.28748110e-002 -5.39388359e-001 8.63527238e-001 -1.52321577e+000 + 3.66858244e-002 -5.30467749e-001 + <_> + -2.17379645e-001 -9.23947096e-002 3.21765751e-001 -1.41515732e-001 + 1.57822341e-001 -7.64278531e-001 + <_> + 2.31490731e-002 -3.68193179e-001 1.12642264e+000 -1.46559095e+000 + -3.62795323e-001 -5.06627738e-001 + <_> + -3.25451285e-001 -3.60633343e-001 6.48146391e-001 -1.86265516e+000 + 4.44571674e-001 -1.34043276e+000 + <_> + -4.61787850e-001 -3.34379643e-001 2.99409598e-001 -1.92086828e+000 + 6.80965960e-001 -1.51841652e+000 + <_> + -3.06608021e-001 -1.47078380e-001 4.65035528e-001 -9.31418419e-001 + 8.21162224e-001 -1.00074029e+000 + <_> + -2.10174859e-001 -4.20232058e-001 7.94946969e-001 -1.82331073e+000 + -1.82887062e-001 -7.36902416e-001 + <_> + -4.44649868e-002 1.54121056e-001 2.13011071e-001 -5.20685196e-001 + 1.49664629e+000 -1.26169598e+000 + <_> + -5.54895759e-001 -5.35739623e-002 -4.84548211e-001 7.68283010e-001 + -2.56994098e-001 -3.12297046e-001 + <_> + -1.03484464e+000 1.98951054e+000 -3.47923368e-001 -1.33564830e+000 + 3.59096503e+000 -3.23902583e+000 + <_> + -1.69712996e+000 1.59831119e+000 -2.57186198e+000 2.02350855e+000 + 2.40038109e+000 -8.79480541e-001 + <_> + -1.23491585e+000 1.80188346e+000 1.30674779e+000 3.36084127e+000 + 2.71640301e+000 -2.30841660e+000 + <_> + -2.20704913e-001 9.55498576e-001 2.64920652e-001 -1.87634623e+000 + -9.75317419e-001 9.67822194e-001 + <_> + -8.54767933e-002 9.56056058e-001 -1.21861303e+000 -2.33580899e+000 + -1.23457980e+000 7.69731998e-001 + <_> + -8.88020769e-002 2.60611653e-001 1.79869962e+000 3.06268483e-001 + 4.68049169e-001 -2.67209947e-001 + <_> + -7.82720149e-002 2.48066083e-001 1.57362401e+000 4.22651619e-001 + 4.19426113e-001 -2.61658788e-001 + <_> + -4.45715636e-001 2.43262555e-002 1.20379493e-001 -1.74405202e-001 + -4.08422709e-001 7.01995015e-001 + <_> + -8.76339614e-001 -7.07878917e-002 5.63293040e-001 -1.00226986e+000 + -6.36273682e-001 7.83663630e-001 + <_> + 8.96547064e-002 1.73551664e-001 1.61049378e+000 -1.12330556e-001 + 1.67281181e-001 -2.58248895e-002 + <_> + 1.64422393e+000 2.15513825e-001 1.41833806e+000 -1.17124224e+000 + 3.72754872e-001 -1.52781606e+000 + <_> + 1.60265791e+000 -1.74989719e-002 3.83110613e-001 4.52845067e-001 + -2.92874366e-001 -2.55701751e-001 + <_> + 1.80106640e+000 2.52301216e-001 1.28213227e+000 -1.17040575e+000 + 4.54252332e-001 -1.43444455e+000 + <_> + 1.91855967e+000 2.10990950e-001 1.37313294e+000 -1.19144678e+000 + 6.37684286e-001 -1.56363010e+000 + <_> + 8.02330747e-002 1.38708130e-001 -1.54498899e+000 -3.03126526e+000 + 2.13705570e-001 -1.34825206e+000 + <_> + 2.09867907e+000 4.70619500e-002 1.24480784e+000 -7.90261209e-001 + -1.54289335e-001 -1.54596722e+000 + <_> + 2.05323362e+000 6.52050367e-003 1.30877149e+000 -4.12936628e-001 + -1.82920005e-002 -1.20455050e+000 + <_> + 1.61762166e+000 -1.25722978e-002 1.24517679e+000 -1.24405541e-001 + -4.88654733e-001 -6.71042919e-001 + <_> + 4.83700603e-001 -3.29036474e-001 3.47119011e-002 -6.00023329e-001 + 2.89319277e-001 -6.69239223e-001 + <_> + 3.68978381e-001 -3.53338361e-001 -1.26076147e-001 -6.63618863e-001 + 3.58789474e-001 -7.25258231e-001 + <_> + 3.61219376e-001 -1.31685078e-001 3.06044996e-001 -9.12790775e-001 + -8.76504257e-002 -2.50477254e-001 + <_> + 9.80830252e-001 -1.66727245e-001 8.71626854e-001 -1.11824262e+000 + -2.58172721e-001 -1.17959462e-001 + <_> + 2.02714294e-001 -2.49135792e-001 7.78031051e-001 -3.13718691e-002 + 4.31404114e-001 -2.60309547e-001 + <_> + 2.42617667e-001 -2.29737073e-001 -4.54380512e-001 -2.13476047e-002 + 4.43662941e-001 -4.75584775e-001 + <_> + 1.47292927e-001 -2.36872852e-001 -4.51328248e-001 -8.86291042e-002 + 3.57857883e-001 -4.08583939e-001 + <_> + 9.79679152e-002 -4.00562346e-001 -2.55313218e-001 2.17638522e-001 + 6.45443320e-001 -3.26536119e-001 + <_> + 1.55051917e-001 -3.91509056e-001 -2.56903648e-001 2.34357312e-001 + 6.46407723e-001 -3.53539675e-001 + <_> + -5.55492602e-002 -5.45957536e-002 2.34028827e-002 -2.18560040e-001 + 2.01476023e-001 -2.70031631e-001 + <_> + -8.93562883e-002 -5.11645749e-002 5.62228449e-002 -2.79729575e-001 + 2.27722362e-001 -2.57528424e-001 + <_> + 5.91218054e-001 1.07978332e+000 8.04977477e-001 3.27645034e-001 + -8.97745311e-001 1.09823442e+000 + <_> + 6.16157711e-001 1.06430602e+000 7.86137819e-001 2.50373423e-001 + -7.84617484e-001 1.00507784e+000 + <_> + 5.05314946e-001 1.05132556e+000 1.05606151e+000 2.57662922e-001 + -8.86135161e-001 1.02620864e+000 + <_> + -1.55861929e-001 1.13215816e+000 9.36714411e-001 1.33645725e+000 + -1.16280031e+000 1.63704610e+000 + <_> + 3.05243820e-001 6.24079108e-001 1.50984585e+000 -2.06916165e+000 + -1.03173220e+000 6.89464033e-001 + <_> + 4.30581113e-003 1.51177537e+000 -8.32908809e-001 2.45177770e+000 + -8.76697600e-001 2.01877284e+000 + <_> + -3.60366762e-001 4.21638548e-001 1.18767664e-001 -1.34907210e+000 + -1.32410908e+000 6.54479623e-001 + <_> + -5.78172684e-001 7.20088363e-001 -3.08878953e-003 -1.70683718e+000 + 2.11505294e+000 -3.31260180e+000 + <_> + -3.62029403e-001 1.58808425e-001 1.60356307e+000 -2.16983867e+000 + 1.76642382e+000 -2.92043519e+000 + <_> + -2.55620390e-001 1.21576764e-001 1.87860560e+000 -2.15617895e+000 + 1.69483149e+000 -2.90182590e+000 + <_> + 1.57268777e-001 -4.37684320e-002 3.64380509e-001 -1.46592820e+000 + 6.42488539e-001 -1.18111610e+000 + <_> + 9.29373316e-003 1.85821339e-001 1.11097336e+000 1.51272520e-001 + 3.04374620e-002 -2.87331372e-001 + <_> + -1.75813615e-001 7.50227928e-001 -1.49445784e+000 3.63455713e-001 + 2.30768286e-002 6.44706845e-001 + <_> + -2.34006047e-001 7.20138609e-001 -1.51007855e+000 3.99896860e-001 + 1.58320799e-001 4.56107855e-001 + <_> + 1.92738444e-001 7.70549774e-001 -1.59572482e+000 2.45419726e-001 + 2.59943485e-001 3.29831868e-001 + <_> + -5.67003433e-003 -1.15901247e-001 2.29222253e-001 1.08763695e+000 + 2.78395414e-001 -3.08749646e-001 + <_> + 5.75103201e-002 -1.12212986e-001 2.12820604e-001 8.90093565e-001 + 4.97824579e-001 -4.39070970e-001 + <_> + -6.05371818e-002 -8.63207653e-002 2.65989661e-001 8.88216436e-001 + 4.74908173e-001 -4.24332142e-001 + <_> + -1.69717267e-001 7.07560852e-002 2.79600948e-001 1.14539278e+000 + 1.75477326e-001 -7.55497962e-002 + <_> + 2.87508965e-001 -1.00374088e-001 -4.58311290e-001 3.36382121e-001 + 1.22160280e+000 -1.17831028e+000 + <_> + 2.84737915e-001 -1.38583809e-001 -5.57647288e-001 1.89165711e-001 + 1.20182002e+000 -1.00754547e+000 + <_> + -1.67500407e-001 7.75158554e-002 -2.36685485e-001 1.43650234e+000 + -4.00695175e-001 8.98085684e-002 + <_> + -1.84169468e-002 8.79091173e-002 1.68189257e-001 1.49459434e+000 + -2.67490476e-001 8.66287202e-003 + <_> + -4.55734134e-002 4.75119501e-002 -8.76740158e-001 1.01937532e+000 + 7.75358438e-001 -1.01215303e+000 + <_> + 2.50930876e-001 -2.48448402e-002 -3.66898686e-001 1.06927502e+000 + 7.83967972e-001 -7.21764922e-001 + <_> + -1.96319520e-001 -4.65975739e-002 -2.59005934e-001 1.12004161e+000 + 3.85244250e-001 -3.19261253e-001 + <_> + -1.79693118e-001 -3.91086526e-002 -1.39651731e-001 1.12749350e+000 + 4.57551390e-001 -4.42965567e-001 + <_> + -1.76367834e-001 3.47063869e-001 -8.63374114e-001 2.21045923e+000 + 6.32735252e-001 -2.05300152e-001 + <_> + -2.12391719e-001 3.14242244e-001 -9.04222310e-001 2.31858039e+000 + 5.02704799e-001 -1.50020942e-001 + <_> + -1.62512496e-001 3.10139149e-001 -6.81537032e-001 2.19840455e+000 + 5.73979616e-001 -2.44098499e-001 + <_> + -2.02799559e+000 1.16889477e+000 8.24881315e-001 2.67122316e+000 + -4.13410485e-001 -4.75319266e-001 + <_> + -2.24584416e-001 -2.41473779e-001 2.30798364e-001 -1.05994135e-001 + 3.31500103e-003 -7.54397782e-003 + <_> + -1.80801541e-001 -2.43621588e-001 3.13839912e-001 -2.06049740e-001 + -7.03305826e-002 8.63120854e-002 + <_> + -1.88888788e+000 -7.36844420e-001 -1.83833092e-001 9.01804328e-001 + 7.30453944e-003 -2.65951958e-002 + <_> + -1.03373623e+000 -7.39877880e-001 6.30378127e-001 9.85903442e-002 + 3.07156622e-001 4.31942135e-001 + <_> + -1.64503384e+000 -9.75142956e-001 -6.66958187e-003 7.79121339e-001 + -2.05061466e-001 9.85035539e-001 + <_> + 6.76012754e-001 -1.01038665e-001 -9.18714523e-001 -7.25177228e-001 + -1.09783196e+000 9.45207059e-001 + <_> + 8.69433284e-001 -3.90263915e-001 -3.72874618e-001 -6.94974542e-001 + -1.24865746e+000 8.94252419e-001 + <_> + -2.86102116e-001 3.62178758e-002 -5.49290538e-001 -7.80998051e-001 + -5.10923982e-001 6.72663629e-001 + <_> + -3.35427135e-001 6.73849881e-002 -6.48167074e-001 -6.33301973e-001 + -6.71062589e-001 7.64219165e-001 + <_> + -2.20150709e-001 -7.42328260e-003 -1.74195424e-001 + -5.92408180e-001 -1.49189770e-001 2.09129289e-001 + <_> + -1.43110740e+000 -2.76198357e-001 -1.53729403e+000 2.54693568e-001 + 2.84287810e-001 -6.21579066e-002 + <_> + 1.28010559e+000 -1.23176677e-002 4.74684723e-002 -2.14602321e-001 + 1.26282340e-002 3.09096634e-001 + <_> + 1.23964798e+000 -2.87200902e-002 -1.19343117e-001 5.92448473e-001 + 3.49066883e-001 2.37339512e-001 + <_> + 1.01962519e+000 3.41549665e-001 3.55071396e-001 5.99499524e-001 + 4.61857527e-001 2.73235738e-001 + <_> + 9.56444860e-001 3.49019557e-001 2.49278486e-001 6.54840231e-001 + 6.48524702e-001 1.42475709e-001 + <_> + 9.62541223e-001 3.67122442e-001 2.59570897e-001 7.97939360e-001 + 2.78604180e-001 2.21582547e-001 + <_> + -1.08472395e+000 -4.25769955e-001 -4.31712478e-001 6.84466287e-002 + 3.07887793e-001 4.18197662e-001 + <_> + 6.52266527e-003 4.11194801e-001 1.19889796e+000 6.71823993e-002 + -2.24279672e-001 4.02401000e-001 + <_> + 1.45076066e-001 2.35171884e-001 6.53553307e-001 -2.49352843e-001 + 4.86345962e-002 -2.42843062e-001 + <_> + 1.86642095e-001 2.51055837e-001 8.59239340e-001 -2.96582609e-001 + 8.33832324e-002 -1.99586496e-001 + <_> + 3.09080649e-002 1.49643362e-001 1.42405689e+000 -2.48348624e-001 + -6.53474033e-001 1.38469869e-002 + <_> + 7.96788633e-002 4.21484858e-001 5.28427958e-001 -1.93815649e-001 + 4.85385388e-001 2.72165835e-001 + <_> + 1.48401350e-001 2.41760388e-001 7.93527961e-001 3.84142369e-001 + -7.64884412e-001 1.00415206e+000 + <_> + 1.48955569e-001 2.22984344e-001 8.19148242e-001 4.08366054e-001 + -7.48664975e-001 1.02472174e+000 + <_> + 2.01051652e-001 2.75206119e-001 1.16249239e+000 -6.05318844e-001 + -8.86347517e-003 1.87318474e-001 + <_> + -5.48245192e-001 -1.88692495e-001 1.03959732e-003 -4.06628214e-002 + 8.85079324e-001 -4.10745740e-001 + <_> + -4.92823809e-001 -1.65133655e-001 -2.30630994e-001 2.22965106e-001 + 1.01501369e+000 -4.73876476e-001 + <_> + -5.59883654e-001 -1.60472646e-001 -4.42328662e-001 7.51363695e-001 + -8.13241452e-002 3.58648956e-001 + <_> + -1.58850408e+000 -1.50573522e-001 -1.51405975e-001 + -5.94227195e-001 -3.15531015e-001 3.97480875e-001 + <_> + -1.38954139e+000 -1.17875353e-001 2.23005507e-002 -7.01800883e-001 + -3.60789955e-001 4.93794590e-001 + <_> + -9.79977548e-001 -2.22209319e-001 4.14273500e-001 -5.79777956e-001 + -1.23360708e-001 1.55943170e-001 + <_> + 1.36762872e-001 2.65333319e+000 1.06049180e-001 -3.20402265e+000 + 2.94329858e+000 -2.93013358e+000 + <_> + -7.18942940e-001 2.39705369e-001 2.50675607e+000 -4.85472493e-002 + 2.42530704e+000 -2.66049147e+000 + <_> + -1.90773118e+000 -1.03523564e+000 2.13881540e+000 -2.77558476e-001 + 2.61647493e-001 -5.24235010e-001 + <_> + -2.16931987e+000 -2.90763474e+000 2.09671497e+000 2.10968447e+000 + 2.84633470e+000 -2.56168556e+000 + <_> + -6.16456084e-002 3.29468584e+000 2.22006178e+000 1.05808342e+000 + 1.34156835e+000 -1.10815811e+000 + <_> + -6.66335300e-002 1.19769417e-001 -2.03525066e-001 3.72294076e-002 + 6.98276982e-002 -9.68306065e-002 + <_> + -6.77419603e-002 5.77785075e-002 2.02077869e-002 7.50029981e-002 + -3.87593925e-001 7.08935261e-001 + <_> + -4.55734134e-002 7.99338296e-002 9.76328924e-002 1.92043036e-002 + -4.88741428e-001 8.02647650e-001 + <_> + -1.45331860e-001 8.05203319e-002 -2.85821378e-001 1.66412568e+000 + 6.61254078e-002 9.44581807e-001 + <_> + -2.03524292e-001 3.27255785e-001 -3.79638612e-001 1.11646581e+000 + 6.01449490e-001 6.48369908e-001 + <_> + -3.43186140e-001 4.78211910e-001 -2.05380097e-002 1.07251203e+000 + -5.20202965e-002 8.21890235e-001 + <_> + -4.51257795e-001 5.10305822e-001 -3.84707421e-001 1.10333490e+000 + -1.05138429e-001 8.43136013e-001 + <_> + -6.98437035e-001 9.42976534e-001 1.12496078e+000 1.81145322e+000 + 8.94843936e-001 8.82436335e-001 + <_> + -3.44891436e-002 2.04155162e-001 -1.83992609e-001 1.53407943e+000 + -4.13844407e-001 8.46094906e-001 + <_> + -1.83018386e-001 2.20247582e-001 -4.57777977e-001 1.54488170e+000 + -4.91881698e-001 9.83026206e-001 + <_> + -2.71138370e-001 2.41883412e-001 2.88931131e-002 1.23522021e-001 + 1.45215094e-001 4.67260271e-001 + <_> + -2.61716723e-001 1.99704230e-001 -2.94138025e-002 8.81778225e-002 + 2.83781469e-001 3.41974944e-001 + <_> + -9.82237086e-002 1.81799717e-002 2.97888219e-001 -2.44873834e+000 + -1.18153095e+000 -7.04430565e-002 + <_> + -1.04874276e-001 1.45854463e-003 3.97981375e-001 -2.51680160e+000 + -1.42190874e+000 1.33423254e-001 + <_> + -3.39349285e-002 4.28479433e-001 1.42178521e-001 -8.17050993e-001 + -1.20653331e+000 2.74357595e-003 + <_> + -4.11397070e-002 4.41221595e-001 1.63022831e-001 -8.41213584e-001 + -1.32159746e+000 1.06187098e-001 + <_> + -4.94529121e-002 4.70854402e-001 1.90521598e-001 -8.12325060e-001 + -1.22747064e+000 3.43539119e-002 + <_> + -1.56458803e-002 5.10540426e-001 1.60168186e-001 -8.55309844e-001 + -1.31229079e+000 1.28784880e-001 + <_> + 4.75344732e-002 1.08742970e-003 2.82659650e+000 -9.89354372e-001 + 3.49494547e-001 -7.16958269e-002 + <_> + -1.53197432e+000 -1.30705380e+000 5.37151337e-001 1.47797525e-001 + -2.61175931e-001 9.17196453e-001 + <_> + -1.67163610e+000 -3.01709861e-001 -6.22175395e-001 + -5.09203732e-001 1.13077261e-001 4.49611515e-001 + <_> + 5.86230159e-001 9.21225846e-002 5.53871654e-002 6.92066729e-001 + -1.39865177e-002 -4.46749151e-001 + <_> + 9.29373316e-003 4.90421839e-002 -1.01891959e+000 1.07416427e+000 + 2.69230139e-002 -3.95925224e-001 + <_> + 4.14381251e-002 -9.55495983e-002 4.05290574e-001 -1.52527869e-001 + -3.81680548e-001 -1.52692467e-001 + <_> + -2.45132968e-002 -8.59439299e-002 1.65506050e-001 -8.69559348e-002 + -4.50991094e-001 -2.44287446e-001 + <_> + 6.52266527e-003 -2.24497598e-002 7.67345846e-001 -1.52444851e+000 + -9.04155910e-001 -2.54022837e-001 + <_> + 7.07687903e-003 -4.64884453e-002 7.33678281e-001 -1.16583407e+000 + -1.00771224e+000 -4.83241417e-002 + <_> + 1.76069383e-002 -1.91522315e-002 8.14748466e-001 -1.20546091e+000 + -7.15659916e-001 -2.47670159e-001 + <_> + 1.48358699e-002 -1.48606360e-001 2.94414580e-001 -4.87215489e-001 + -3.52537960e-001 -1.25600472e-001 + <_> + -5.33281386e-001 4.35370415e-001 -1.51201665e+000 3.58038634e-001 + -5.07594645e-002 5.44597328e-001 + <_> + -6.72388971e-001 5.56238234e-001 -1.62904239e+000 9.73822773e-002 + 1.84975147e-001 1.90008298e-001 + <_> + -3.22722867e-002 4.89260137e-001 -1.25038779e+000 9.99815688e-002 + 2.40095958e-001 2.07191184e-001 + <_> + 7.07687903e-003 6.36462808e-001 -2.07621765e+000 5.82815468e-001 + 2.96676785e-001 2.33582646e-001 + <_> + 1.30112305e-001 3.00616443e-001 -1.62667379e-001 5.56141734e-002 + 7.85669982e-002 7.99462318e-001 + <_> + -4.87281680e-001 4.09543604e-001 -1.39866221e+000 4.61150348e-001 + -1.62667096e-001 1.22131753e+000 + <_> + 1.46738708e-001 -3.30759019e-001 1.71771514e+000 -1.98872119e-001 + -8.02231312e-001 1.77543357e-001 + <_> + 7.63535798e-002 -2.22434729e-001 1.53481376e+000 3.69978756e-001 + -2.15453595e-001 -3.69154923e-002 + <_> + -7.82720149e-002 -4.33828086e-002 8.84216785e-001 -2.14916661e-001 + 7.57690907e-001 -9.43836451e-001 + <_> + -1.56458803e-002 -1.40583053e-001 1.50936735e+000 -4.36284900e-001 + 5.27662694e-001 -8.89658988e-001 + <_> + -3.98607492e-001 5.31952262e-001 -3.44883025e-001 1.40131330e+000 + -2.00708532e+000 2.35297608e+000 + <_> + -4.40173507e-001 2.53697783e-001 -8.31539810e-001 -1.02632836e-001 + -8.17211986e-001 3.67365450e-001 + <_> + -6.95111752e-001 3.85279715e-001 -1.38473415e+000 1.68680322e+000 + -1.30275738e+000 1.26752234e+000 + <_> + -6.69617891e-001 3.63921791e-001 -1.34892392e+000 1.62512517e+000 + -1.36663020e+000 1.32421434e+000 + <_> + -2.69475728e-001 2.27958843e-001 -9.72397804e-001 1.64726302e-001 + -7.66786695e-001 5.25692165e-001 + <_> + -2.80559987e-001 2.20120057e-001 -9.98279989e-001 1.70761913e-001 + -8.05603385e-001 5.70884347e-001 + <_> + -3.18800718e-001 2.34252676e-001 -1.10312772e+000 6.11498505e-002 + -5.95619202e-001 4.30443287e-001 + <_> + -1.99090585e-001 7.54329383e-001 -3.99259150e-001 4.50540408e-002 + -1.36903656e+000 1.28977847e+000 + <_> + -2.17933849e-001 3.00028294e-001 -6.31941617e-001 -9.27206203e-002 + 1.49105817e-001 -1.31946236e-001 + <_> + -1.32030740e-001 3.14294159e-001 -4.86088604e-001 -7.29991496e-002 + 1.36326909e-001 -1.11492842e-001 + <_> + -9.63905334e-001 -1.06638499e-001 -5.50738096e-001 + -9.11272883e-001 3.70969206e-001 -8.42693001e-002 + <_> + -1.07918179e+000 4.06718552e-001 2.19506010e-001 8.21398973e-001 + -3.03745180e-001 1.21199238e+000 + <_> + 7.13656619e-002 1.85550764e-001 4.33048874e-001 -9.86325927e-003 + 1.71114177e-001 -1.90678522e-001 + <_> + -3.00554335e-002 5.98768629e-002 -2.56833839e+000 1.01980135e-001 + 4.06357378e-001 -2.47057468e-001 + <_> + -1.15289223e+000 -3.90833132e-002 -1.42142102e-001 1.20053692e-002 + -1.52820051e-001 7.10890114e-001 + <_> + -1.03872418e+000 1.50234504e-002 -1.44044399e-001 6.10464737e-002 + -1.97053298e-001 6.96398377e-001 + <_> + -9.15688753e-001 -2.42957938e-003 1.22167513e-001 1.18738614e-001 + -7.75275230e-002 5.77954650e-001 + <_> + -5.92582226e-001 -2.87760556e-001 -5.24401188e-001 1.40244293e+000 + -7.28980064e-001 1.31418276e+000 + <_> + -7.45545208e-001 -1.68478787e-001 -6.12680554e-001 1.28498709e+000 + -2.32364371e-001 3.97547513e-001 + <_> + -2.06849575e-001 -1.78462312e-001 6.76357031e-001 9.31976259e-001 + -1.72833055e-002 9.71893787e-001 + <_> + -1.95765302e-001 -1.93521082e-001 7.29214191e-001 4.11090195e-001 + 1.11939192e-001 7.64313757e-001 + <_> + -4.83402163e-001 -2.69831389e-001 3.16807348e-003 9.82200921e-001 + 3.88241589e-001 4.05465126e-001 + <_> + -4.77860034e-001 -2.65443802e-001 -1.10979207e-001 1.00034750e+000 + 2.92722225e-001 4.42137450e-001 + <_> + -6.81810617e-001 1.08908586e-001 -3.73500794e-001 -1.42502785e-001 + 4.13749784e-001 9.55264866e-002 + <_> + -5.28847694e-001 1.13849156e-001 -2.26307377e-001 -2.24633619e-001 + 5.71306407e-001 2.27559190e-002 + <_> + -3.99161696e-001 4.06271607e-001 -5.76997059e-004 8.37610066e-002 + 2.25391015e-001 -2.19674502e-002 + <_> + -5.43811440e-001 4.30808723e-001 1.95471376e-001 2.72557557e-001 + -2.12867260e-002 6.03837855e-002 + <_> + -1.66946203e-001 2.65231997e-001 1.46516427e-001 1.02017903e+000 + -3.95271927e-002 3.36826622e-001 + <_> + -3.54824603e-001 1.44351497e-001 -3.90614301e-001 1.99759215e-001 + -2.96614647e-001 5.24227917e-001 + <_> + -3.75330508e-001 1.63811117e-001 -5.03154516e-001 2.18473002e-001 + -1.91121563e-001 4.48328435e-001 + <_> + 3.24087083e-001 2.20874965e-001 1.40862656e+000 9.25446391e-001 + -2.41272837e-001 2.11801931e-001 + <_> + 7.13656619e-002 2.24951088e-001 9.80338693e-001 1.03075421e+000 + 7.95322731e-002 -2.74050068e-002 + <_> + 6.67699575e-001 1.71372175e-001 5.19833088e-001 4.08884913e-001 + -7.95003474e-001 3.59263271e-001 + <_> + 6.86542809e-001 1.90504834e-001 4.09171373e-001 3.57418269e-001 + -9.30061400e-001 4.14393246e-001 + <_> + 3.42930347e-001 8.78091902e-002 9.48106766e-001 8.11561227e-001 + -3.00930262e-001 5.12442172e-001 + <_> + 5.13628125e-001 5.77928424e-001 1.11107087e+000 7.34115601e-001 + -3.89629811e-001 5.99891424e-001 + <_> + -1.01037435e-002 9.05553550e-002 7.75664508e-001 9.55452800e-001 + -1.55901432e-001 4.64639157e-001 + <_> + -2.24584416e-001 3.23066860e-001 3.54669034e-001 2.69143134e-001 + -3.40127468e-001 5.63313439e-002 + <_> + -2.36222893e-001 3.54109943e-001 3.54088098e-001 2.47385368e-001 + -6.15708530e-001 3.34145069e-001 + <_> + -2.45644525e-001 4.03510123e-001 2.67244369e-001 2.40438551e-001 + -5.03628373e-001 2.05803782e-001 + <_> + 3.03538516e-002 -1.39369786e+000 2.06893039e+000 -4.53392565e-001 + -4.10469145e-001 4.00347263e-001 + <_> + -1.06477225e+000 -1.34230137e+000 4.42900434e-002 5.15167475e-001 + 5.20376861e-001 -3.17782462e-001 + <_> + -1.15289223e+000 -1.30835390e+000 -3.62794131e-001 5.97889304e-001 + 4.09462243e-001 -2.93484241e-001 + <_> + 6.79338038e-001 -2.33986497e-001 1.27963233e+000 -2.79122472e-001 + -3.99949491e-001 1.18278399e-001 + <_> + 2.04935408e+000 -2.58019060e-001 2.06773251e-001 -7.00999022e-001 + -3.73669475e-001 -3.24235946e-001 + <_> + -2.56217252e-002 1.21840797e-001 -2.74943292e-001 -4.17461753e-001 + 2.88360044e-002 4.04591635e-002 + <_> + -1.91331595e-001 8.05983990e-002 -1.10209918e+000 6.90145671e-001 + -4.53629345e-001 1.00796068e+000 + <_> + 1.09052189e-001 1.83167547e-001 3.56261820e-001 -6.91060305e-001 + -2.19259191e+000 1.81656265e+000 + <_> + 3.01364303e-001 3.24656755e-001 2.96490669e-001 -1.09278524e+000 + -1.28411591e+000 1.16217160e+000 + <_> + 1.40642360e-001 3.21577072e-001 2.12675363e-001 -1.18377435e+000 + -1.24224091e+000 1.13943553e+000 + <_> + 1.67798832e-001 2.24479020e-001 3.64404321e-001 -1.08155215e+000 + -7.14320362e-001 3.90948117e-001 + <_> + 2.04931140e-001 2.48244688e-001 6.01772249e-001 -1.05126882e+000 + -8.99846613e-001 5.72490036e-001 + <_> + 1.36208653e-001 3.03353637e-001 4.46141124e-001 -1.09235835e+000 + -7.78753698e-001 4.55209911e-001 + <_> + 1.58931404e-001 1.54270843e-001 -3.31219345e-001 -5.92224896e-001 + -9.35684383e-001 5.32986462e-001 + <_> + 1.17365390e-001 1.56712934e-001 -2.97675610e-001 -5.40164411e-001 + -9.25104499e-001 4.25222099e-001 + <_> + -9.21273604e-002 2.30824351e-001 6.53841376e-001 -2.15697074e+000 + -4.25460517e-001 1.74570113e-001 + <_> + 4.18857604e-001 2.26384878e-001 -2.60732025e-001 -1.37587130e+000 + -8.56555820e-001 8.32265198e-001 + <_> + 3.59556735e-001 2.32581019e-001 -2.26514518e-001 -1.46378720e+000 + -9.30081904e-001 9.23352242e-001 + <_> + 2.40400806e-001 2.57699549e-001 -5.20237088e-001 -1.45105231e+000 + -9.08634722e-001 8.92742932e-001 + <_> + -1.50916660e-002 1.84332788e-001 -1.39626467e+000 -2.70031273e-001 + 1.80612758e-001 1.68562114e-001 + <_> + 4.19923365e-002 1.75461590e-001 -1.45962369e+000 -3.45402539e-001 + 3.85867834e-001 -6.94051851e-003 + <_> + 6.56061053e-001 2.86596447e-001 -2.77419388e-001 -2.02132568e-001 + 4.32358384e-001 2.42197793e-002 + <_> + 9.29373316e-003 5.87171279e-002 -1.32826054e+000 -1.33749098e-001 + 1.88319087e-002 -8.63265526e-003 + <_> + 2.42063448e-001 9.10129175e-002 -9.01720047e-001 -1.06616870e-001 + 1.70638263e-001 -4.98384416e-001 + <_> + 1.94401085e-001 9.04284492e-002 -9.64517236e-001 -7.46360188e-003 + 1.92882642e-001 -5.18708944e-001 + <_> + 9.61432755e-001 5.93344942e-002 1.71579584e-001 1.76012412e-001 + -6.61524385e-002 -3.10118884e-001 + <_> + 8.79409134e-001 6.60358146e-002 4.37686406e-002 3.10286820e-001 + -1.50967672e-001 -2.54105240e-001 + <_> + 2.01051652e-001 -3.22221816e-002 -7.97910392e-001 -3.21850879e-003 + 1.58103958e-001 -8.39113742e-002 + <_> + -1.08804917e+000 2.06669584e-001 -8.47534359e-001 -5.56119025e-001 + 4.12979782e-001 -4.68597412e-001 + <_> + 7.69120634e-001 1.57758832e-001 8.98337603e-001 -4.99518998e-002 + 7.51016676e-001 -1.18933451e+000 + <_> + 5.72374821e-001 1.20561920e-001 9.70905781e-001 -2.77624913e-002 + 8.80976140e-001 -1.22342467e+000 + <_> + 1.13485895e-001 3.96024585e-002 -4.36105132e-001 -4.32947278e-002 + 5.99323809e-001 -1.11531281e+000 + <_> + 6.64374292e-001 1.05112426e-001 4.81349081e-001 -8.80953744e-002 + 8.38587940e-001 -1.22958267e+000 + <_> + 1.31732291e-002 7.49811977e-002 7.01355934e-001 2.89721221e-001 + -2.26180896e-001 -4.61219549e-001 + <_> + -1.46602285e+000 6.43594086e-001 3.20127726e-001 8.54794741e-001 + -1.83729857e-001 -9.56165433e-001 + <_> + -1.11686826e+000 8.92854989e-001 2.60920823e-001 -1.06381111e-001 + 8.64562988e-001 -1.81244314e+000 + <_> + 2.49822438e-001 -3.91997956e-002 -1.15502812e-001 5.09515822e-001 + -5.12048721e-001 -1.53776005e-001 + <_> + 5.71266353e-001 3.13394994e-001 1.14714313e+000 7.93927014e-001 + 1.26816526e-001 -1.11021519e-001 + <_> + 5.94543338e-001 3.06578815e-001 1.23807240e+000 9.61356819e-001 + -1.91956326e-001 -7.18808093e-004 + <_> + -4.24101323e-001 3.72846931e-001 -3.17991346e-001 7.02338874e-001 + 2.19578817e-001 -2.18085080e-001 + <_> + 8.67216468e-001 2.87281368e-002 -1.76747695e-001 -9.49535251e-001 + -7.35983610e-001 -2.20501363e-001 + <_> + 8.62228513e-001 2.93448903e-002 -1.11298241e-001 -9.09430087e-001 + -7.85189390e-001 -1.55785754e-001 + <_> + -1.19778347e+000 -3.36227447e-001 6.25978351e-001 -6.03377879e-001 + 6.70675188e-002 -5.45484900e-001 + <_> + -1.24655426e+000 -3.88893068e-001 4.82153803e-001 -5.69657028e-001 + 8.14279243e-002 -3.89886081e-001 + <_> + -1.19944608e+000 -8.06608677e-001 -4.27655518e-001 + -1.27377558e+000 -5.96363246e-001 5.71335793e-001 + <_> + -1.20277143e+000 -7.31488764e-001 -6.50764927e-002 + -1.41785419e+000 -1.68973863e-001 2.80350566e-001 + <_> + -9.31206763e-001 -6.59801245e-001 2.62289822e-001 -1.32195377e+000 + -7.74491191e-001 -6.07788384e-001 + <_> + -1.01600146e+000 -5.43045759e-001 1.09653436e-002 -9.54364240e-001 + -6.39788747e-001 -4.45764989e-001 + <_> + 7.29217231e-001 -2.13768303e-001 3.85220051e-001 -6.02757037e-001 + -2.97223598e-001 -1.01939902e-001 + <_> + 7.66349554e-001 -1.37772098e-001 5.78175724e-001 -4.93636400e-001 + -4.63939667e-001 -1.17311820e-001 + <_> + 7.45843649e-001 -2.01331258e-001 6.45577431e-001 -5.35765469e-001 + -5.29849946e-001 -8.71004388e-002 + <_> + -1.27898049e-004 -1.17874533e-001 -3.16396207e-001 + -9.02415931e-001 -1.08761281e-001 -3.82849514e-001 + <_> + -5.11582056e-003 -9.98562500e-002 -3.06672841e-001 + -7.62375355e-001 -8.76597166e-002 -5.37661850e-001 + <_> + -3.61517817e-002 -9.54371989e-002 -4.31819618e-001 + -6.85274661e-001 -3.24429981e-002 -3.48483115e-001 + <_> + -7.16214553e-002 -8.31352323e-002 -4.83722031e-001 + -7.03828633e-001 -3.64989452e-002 -4.56256956e-001 + <_> + 3.57894093e-001 4.24099341e-002 6.91294074e-002 -6.85716927e-001 + 4.31217998e-001 -6.46576762e-001 + <_> + 1.31732291e-002 -1.42993867e-001 4.94326726e-002 -4.52305786e-002 + -4.56113815e-001 -2.72005647e-001 + <_> + -4.16939184e-002 -1.06605798e-001 -1.95894465e-001 + -1.15210128e+000 -1.04762638e+000 1.16784610e-001 + <_> + 1.37543035e+000 2.30506510e-002 6.28552020e-001 -1.74585795e+000 + 5.15793487e-002 -3.95664334e-001 + <_> + 2.11530542e+000 2.85954744e-001 1.25383830e+000 -7.54411101e-001 + -1.14472389e+000 7.90596008e-001 + <_> + 8.57752115e-002 -6.31375432e-001 -1.09657839e-001 -2.40440726e+000 + 5.32490969e-001 -3.24784756e-001 + <_> + 1.98280573e-001 -6.57832265e-001 1.74262792e-001 -2.59976792e+000 + 4.28207040e-001 -3.19324374e-001 + <_> + 8.92156065e-001 -6.66201591e-001 -5.98602593e-001 -7.91961372e-001 + 2.70971119e-001 -1.76868546e+000 + <_> + 1.26680446e+000 -7.36835420e-001 -4.20060977e-002 -1.00698113e+000 + 5.36888957e-001 -1.94535935e+000 + <_> + 1.43528545e+000 -3.05268312e+000 -6.54983461e-001 -1.26823410e-001 + -1.53818429e+000 1.62059569e+000 + <_> + 1.26569605e+000 -3.19101405e+000 1.59203947e-001 2.32302737e+000 + -1.25229216e+000 2.36750913e+000 + <_> + 1.63923597e+000 -1.54101169e+000 -1.35661161e+000 4.93048519e-001 + -3.03539491e+000 3.13190675e+000 + <_> + 8.02330747e-002 -3.04753363e-001 7.44623005e-001 -5.47466874e-001 + 1.46740898e-002 -2.86163568e-001 + <_> + 2.81966835e-001 -6.88343167e-001 -3.96914035e-001 -1.38994491e+000 + -4.79147077e-001 -8.41863871e-001 + <_> + 1.67244613e-001 -6.70563161e-001 -6.09364033e-001 -1.52313173e+000 + -4.53596681e-001 -8.77421916e-001 + <_> + -7.21756667e-002 1.44172683e-001 -5.15753925e-001 1.81607187e+000 + 9.63601351e-001 3.06446105e-001 + <_> + -6.88503906e-002 1.38324752e-001 -3.48994732e-001 1.20049775e+000 + 1.22541547e+000 -1.92693859e-001 + <_> + 2.70285700e-002 -9.86832306e-002 2.67408285e-002 1.46373308e+000 + 5.32925904e-001 5.15355229e-001 + <_> + -8.99105072e-002 -1.46379536e-002 -2.47537389e-001 5.68391025e-001 + 6.54500067e-001 9.53705907e-002 + <_> + 1.25350332e+000 -2.97359675e-001 2.54728532e+000 1.60029322e-001 + -4.53210771e-001 -2.62431324e-001 + <_> + 1.27567184e+000 -3.13143522e-001 2.19135118e+000 -1.94184706e-001 + -6.61954343e-001 -2.16274455e-001 + <_> + -2.05293512e+000 -1.45823145e+000 8.02510917e-001 1.38560224e+000 + 8.18152905e-001 -5.92865884e-001 + <_> + 1.76227140e+000 -3.03725791e+000 -1.35020483e+000 1.53252587e-001 + -4.58593905e-001 -2.63655037e-002 + <_> + 1.82656026e+000 -2.94357133e+000 -9.94027793e-001 -1.86668265e+000 + -8.83996189e-001 5.36829174e-001 + <_> + 1.90415013e+000 -2.77281404e+000 -9.42203939e-001 -1.95057738e+000 + -1.95300007e+000 1.01267958e+000 + <_> + -2.78385784e-002 2.53772378e-001 -9.25585628e-001 4.75824893e-001 + -4.43473458e-002 6.52203560e-001 + <_> + 1.54497698e-001 5.85530162e-001 -8.74614060e-001 1.63755596e+000 + 3.38455647e-001 4.12075341e-001 + <_> + 5.97271733e-002 4.37784284e-001 -9.59379375e-001 1.56309593e+000 + 4.69524235e-001 4.69166487e-001 + <_> + -2.11880151e-002 1.92203596e-002 -6.69382811e-001 5.26997507e-001 + 4.68413353e-001 5.44719934e-001 + <_> + 1.34546012e-001 8.81815255e-002 -5.53673685e-001 1.06265210e-001 + -1.05945885e-001 8.13863516e-001 + <_> + 3.14111233e-001 2.51742691e-001 -3.85674059e-001 1.08090162e+000 + -2.96581119e-001 8.42863560e-001 + <_> + 4.36549783e-002 3.34750444e-001 -1.16027749e+000 1.73286593e+000 + 3.52664977e-001 6.85112894e-001 + <_> + -3.11638601e-002 3.46571147e-001 -1.16556537e+000 1.79702210e+000 + 3.11819077e-001 8.39463234e-001 + <_> + -3.22722867e-002 -6.90020144e-001 -5.26726339e-003 2.01288372e-001 + 8.30669165e-001 -5.23243487e-001 + <_> + 4.85917449e-001 -7.36556649e-001 2.06765914e+000 -2.43811145e-001 + 7.97423899e-001 -3.62489283e-001 + <_> + 4.27170813e-001 -7.38257766e-001 1.99609554e+000 -2.21727416e-001 + 7.26853132e-001 -3.00274581e-001 + <_> + 1.51726633e-001 -4.01582092e-001 -1.16343252e-001 7.30986059e-001 + 5.44318914e-001 -3.86321187e-001 + <_> + 6.24982417e-002 7.51522481e-002 -5.19175567e-002 8.66783112e-002 + -2.64115244e-001 7.15030611e-001 + <_> + 8.68836418e-002 -1.82253167e-001 6.99265540e-001 -2.26383105e-001 + -3.51949871e-001 8.40416789e-001 + <_> + 1.93846866e-001 -2.86582649e-001 9.00247037e-001 8.28575045e-002 + -9.86394882e-002 5.83899677e-001 + <_> + 1.73340961e-001 -1.76923499e-001 3.60744953e-001 3.20637703e-001 + 3.39599401e-001 3.72785814e-002 + <_> + 2.53701925e-001 -2.58479267e-001 1.36045456e-001 -1.12215623e-001 + 1.68965653e-001 2.28660867e-001 + <_> + 5.26375055e-001 1.82412788e-002 9.25607800e-001 -7.59790361e-001 + 6.76066697e-001 4.57612038e-001 + <_> + 4.90905374e-001 7.63706341e-002 4.01395530e-001 -6.49529338e-001 + 3.02439630e-001 7.76552200e-001 + <_> + 1.45468295e+000 -1.63794899e+000 -8.28732789e-001 -3.49713302e+000 + -2.22423315e+000 1.98511469e+000 + <_> + 3.03538516e-002 -7.30675220e-001 -1.17590535e+000 1.55179274e+000 + 9.76320624e-001 -4.93230760e-001 + <_> + 1.48358699e-002 -1.31335521e+000 -6.17471465e-004 1.06852341e+000 + 1.63392293e+000 -1.35678732e+000 + <_> + -1.03207362e+000 -5.59978560e-002 8.17764550e-002 -4.10618097e-001 + -7.22705424e-001 6.57886118e-002 + <_> + -8.99105072e-002 -1.24568500e-001 -1.60783613e+000 3.21841799e-002 + -4.76272315e-001 2.95407265e-001 + <_> + -6.68509483e-001 -1.03558198e-001 -2.19107702e-001 1.80358008e-001 + -6.01341784e-001 3.23415957e-002 + <_> + 8.24541986e-001 -5.32972589e-002 5.13278246e-002 1.12622130e+000 + 5.42133629e-001 -9.70168591e-001 + <_> + 9.78059173e-001 -1.64179150e-002 -1.98139593e-001 1.02758861e+000 + 7.87998855e-001 -9.82893109e-001 + <_> + -2.27739167e+000 -9.06997621e-002 4.59166735e-001 1.55965492e-001 + -6.96332872e-001 -8.38611871e-002 + <_> + -5.17209232e-001 -6.84745535e-002 1.18393868e-001 8.71474668e-002 + -6.85848653e-001 5.44346720e-002 + <_> + -1.03429043e+000 -8.95847827e-002 1.50547192e-001 -7.79390514e-001 + -8.53380919e-001 5.63440859e-001 + <_> + -9.39519942e-001 -2.62806356e-001 4.44191217e-001 2.72636116e-001 + -1.10053718e+000 1.19369543e+000 + <_> + -1.80076790e+000 -2.08247975e-001 -6.00852549e-001 + -5.53778946e-001 1.26289740e-001 7.35953301e-002 + <_> + -1.47101080e+000 -2.24097177e-001 6.58938438e-002 -5.57756543e-001 + -5.64289391e-002 1.91338301e-001 + <_> + -1.57464874e+000 -1.00353248e-001 -9.72583890e-002 + -4.35196906e-001 7.83615038e-002 1.35554284e-001 + <_> + -5.66534221e-001 -1.35926738e-001 9.70891476e-001 1.48041773e+000 + -2.02886581e+000 1.72947061e+000 + <_> + -2.83885270e-001 -1.17526308e-001 1.74929464e+000 1.38603234e+000 + -1.98623121e+000 1.67644453e+000 + <_> + -2.16599464e+000 -6.18668273e-002 -1.00015843e+000 3.88787746e-001 + -8.68601084e-001 1.07759392e+000 + <_> + -1.99751377e+000 -3.26398909e-002 -2.91547328e-001 1.50083199e-001 + -6.57848597e-001 9.83773172e-001 + <_> + -6.21401310e-001 -3.00542772e-001 3.28834474e-001 1.75049973e+000 + -9.34226394e-001 1.09227467e+000 + <_> + -1.59293783e+000 -2.85944100e-002 -1.37824643e+000 1.67193753e-003 + -4.40148741e-001 7.58821964e-001 + <_> + -1.60125101e+000 -2.01552231e-002 -1.36300898e+000 + -6.85636653e-004 -4.96127397e-001 7.18142569e-001 + <_> + -1.73758757e+000 1.75219364e-002 -1.20588267e+000 3.94920819e-002 + -4.17617589e-001 7.54644752e-001 + <_> + -2.55620390e-001 4.30438429e-001 8.90264153e-001 9.10381228e-002 + 3.13201457e-001 -1.62483260e-001 + <_> + -2.76126295e-001 4.27978367e-001 1.61612916e+000 5.43484613e-002 + 4.55003947e-001 -1.54926866e-001 + <_> + -3.38752419e-001 3.54187369e-001 -2.30759904e-002 5.57228267e-001 + -2.52668530e-001 6.61104798e-001 + <_> + -2.62825161e-001 2.84662604e-001 2.14032456e-001 5.16250670e-001 + -3.50068778e-001 7.63273954e-001 + <_> + 2.95267969e-001 9.96664762e-002 1.18344867e+000 1.22314429e+000 + -1.00989200e-001 2.31189609e-001 + <_> + -2.20150709e-001 1.66363358e-001 -1.48759151e+000 6.62965700e-002 + -4.45177704e-001 1.40499055e-001 + <_> + 3.04135382e-001 1.43383250e-001 -3.95775974e-001 1.24340639e-001 + -4.56979245e-001 2.30881870e-001 + <_> + 6.78229630e-001 9.95015576e-002 2.24167705e-001 5.26237905e-001 + -3.94887775e-001 1.62736207e-001 + <_> + -3.82535309e-001 1.54061794e-001 -3.30095589e-001 2.04990119e-001 + -2.39323169e-001 3.85090858e-001 + <_> + -3.95836413e-001 1.41474128e-001 -2.42385238e-001 1.34962171e-001 + -2.58433640e-001 3.83095592e-001 + <_> + -1.48102939e-001 3.86567354e-001 -2.17692554e-003 -1.56069979e-001 + -4.64650571e-001 2.41775066e-001 + <_> + -2.21259132e-001 3.96842927e-001 -2.67543644e-001 -1.63110450e-001 + -6.77170634e-001 4.61301506e-001 + <_> + 2.08895653e-003 -2.87807845e-002 -3.98856789e-001 3.12803328e-001 + -1.37636602e-001 1.63903415e-001 + <_> + 4.30581113e-003 7.56722735e-003 -3.57508630e-001 3.96231443e-001 + -2.18517005e-001 3.21152985e-001 + <_> + -6.18076026e-001 -1.11920786e+000 1.42190427e-001 7.71233618e-001 + 1.39003205e+000 -1.12577522e+000 + <_> + -9.83857036e-001 -1.18086922e+000 -6.95179284e-001 + -3.02726924e-001 1.16535711e+000 -1.04334664e+000 + <_> + -3.79764229e-001 -1.20263052e+000 2.66968191e-001 -8.34590256e-001 + 2.09004116e+000 -2.93469071e+000 + <_> + -1.16512761e-001 -1.52693963e+000 1.38611567e+000 -2.07585382e+000 + 6.87250137e-001 -1.43531430e+000 + <_> + 3.54014605e-001 -9.31079239e-002 3.47452700e-001 -9.13227797e-001 + -4.89956528e-001 1.70031592e-001 + <_> + 2.65894651e-001 -8.91180262e-002 1.56815961e-001 -1.06695640e+000 + -3.27974796e-001 1.49350107e-001 + <_> + 3.76737356e-001 -9.99490246e-002 2.00194970e-001 -1.07950997e+000 + -3.34731460e-001 1.97037593e-001 + <_> + 3.57339889e-001 -1.38668418e-001 1.62053823e-001 -1.03100657e+000 + -3.23724508e-001 2.10577071e-001 + <_> + 2.15687156e+000 1.39929450e+000 1.82535553e+000 1.28875637e+000 + -2.87885107e-002 2.74541438e-001 + <_> + 1.55606136e-001 -5.47653615e-001 -5.13932586e-001 1.26309168e+000 + 1.54101014e+000 -6.00629687e-001 + <_> + 7.96788633e-002 -5.50425172e-001 -7.84834743e-001 9.40852642e-001 + 1.66064155e+000 -9.68470275e-001 + <_> + 1.86590934e+000 7.53272474e-001 -8.90037239e-001 4.81579334e-001 + -4.76462871e-001 6.26609743e-001 + <_> + 3.17990720e-001 -2.08447009e-001 -1.69612980e+000 2.07794213e+000 + -2.69658327e+000 2.90946221e+000 + <_> + 9.49240088e-001 -9.28691685e-001 2.95088863e+000 -2.24987239e-001 + 1.98605999e-001 -3.89703959e-001 + <_> + -3.00554335e-002 -4.87278029e-002 -1.05241561e+000 + -3.30622077e-001 -8.10425758e-001 5.91851711e-001 + <_> + -5.44408336e-002 -3.48391943e-002 -1.23697174e+000 + -5.84458411e-001 -1.01482284e+000 7.51939356e-001 + <_> + -1.03211634e-001 -1.47656500e-001 -1.52962291e+000 + -3.38035971e-001 -1.10877204e+000 7.76779234e-001 + <_> + 3.03538516e-002 1.84914134e-002 -3.14393908e-001 -9.45895672e-001 + -6.00523591e-001 4.67884034e-001 + <_> + 2.86912099e-002 -1.43331468e-001 -4.17863071e-001 -1.31645226e+000 + 6.54650182e-002 3.03752739e-002 + <_> + -3.45317973e-003 -1.68743432e-001 -5.06392479e-001 + -1.19284189e+000 -8.01140592e-002 1.09077521e-001 + <_> + -2.83927917e-002 1.57440856e-001 3.99938434e-001 -8.69755983e-001 + -7.44795322e-001 5.80021858e-001 + <_> + 2.37032883e-002 1.52263224e-001 4.87370253e-001 -1.10698891e+000 + -1.20959520e+000 9.21729088e-001 + <_> + 1.40642360e-001 -1.30349658e-002 -2.50418216e-001 -1.42270446e+000 + 2.92152554e-001 -6.18114956e-002 + <_> + 8.52210000e-002 -1.39959544e-001 -1.13443382e-001 -1.43148541e+000 + 4.49336432e-002 9.84973609e-002 + <_> + -2.48969808e-001 -3.47819738e-002 3.71099263e-001 -1.00913036e+000 + -1.38424420e+000 1.20902848e+000 + <_> + 9.40884203e-002 1.10547543e-001 -9.30109620e-002 -8.81332695e-001 + -5.14769256e-001 2.31746152e-001 + <_> + 1.22353315e-001 6.97604567e-002 2.11982206e-002 -1.14799535e+000 + -3.43765557e-001 1.18327126e-001 + <_> + -6.71877488e-002 -4.05292422e-001 5.91660798e-001 -1.45080113e+000 + -2.67357916e-001 2.43483856e-001 + <_> + 1.81611516e-002 -4.92556334e-001 8.09360623e-001 -1.09267199e+000 + -7.15478837e-001 4.44018781e-001 + <_> + -9.54952929e-003 -4.64737028e-001 7.31811702e-001 -2.18189955e+000 + -6.08281910e-001 1.17884070e-001 + <_> + 1.92410183e+000 -2.40798664e+000 1.39995551e+000 -2.36814690e+000 + -8.03043008e-001 1.27395988e+000 + <_> + 9.66420710e-001 -1.13461924e+000 -1.28902900e+000 3.95136452e+000 + -6.93623364e-001 1.03276026e+000 + <_> + 7.23675132e-001 2.85920203e-001 -2.79024065e-001 6.35838136e-002 + 1.27309263e+000 -8.20364118e-001 + <_> + 8.78854930e-001 2.11281136e-001 1.84074268e-001 -4.93690819e-002 + 1.06321824e+000 -6.10010087e-001 + <_> + 8.21770906e-001 2.02742636e-001 7.01246038e-002 -8.71805288e-003 + 1.27664161e+000 -7.01483250e-001 + <_> + 8.62228513e-001 1.61085382e-001 2.77010590e-001 1.06493019e-001 + 1.12922156e+000 -6.81316614e-001 + <_> + -4.94529121e-002 1.91537857e-001 -8.46041560e-001 1.45699486e-001 + 1.05133615e-001 -6.46653678e-003 + <_> + 1.28449664e-001 2.03076959e-001 -4.77929443e-001 3.11465144e-001 + -2.86194570e-002 1.23317331e-001 + <_> + 6.22808218e-001 9.20175388e-002 5.17530799e-001 -6.38581291e-002 + -4.47929800e-002 5.62825084e-001 + <_> + 2.73653626e-001 6.36104867e-002 -8.13669205e-001 7.69496933e-002 + 1.73294209e-002 3.70082945e-001 + <_> + -1.37572870e-001 -9.28614020e-001 3.87736224e-002 -1.18484750e-001 + 1.86770225e+000 -8.93839002e-001 + <_> + 1.83930719e+000 9.71434653e-001 -5.87145984e-001 -8.39892745e-001 + 5.69418073e-003 -8.62483680e-002 + <_> + -6.34148240e-001 3.63149457e-002 -4.16180193e-002 -7.86609650e-001 + 6.42644227e-001 1.81269139e-001 + <_> + 1.50068259e+000 9.18378711e-001 5.06322691e-003 -9.14970636e-001 + 8.55464280e-001 -1.10308230e+000 + <_> + 1.51509213e+000 1.01610434e+000 -1.48179919e-001 -9.60369229e-001 + 9.22610283e-001 -1.03774798e+000 + <_> + 3.74520510e-001 -1.97825685e-001 4.92819995e-001 4.79268223e-001 + 1.00667059e-001 -2.15355262e-001 + <_> + 3.98905903e-001 -2.29968190e-001 6.26264036e-001 5.31444669e-001 + 1.92048416e-001 -2.07017049e-001 + <_> + -7.00653851e-001 -1.06363021e-001 -3.55213493e-001 1.26762986e+000 + 1.19780414e-001 1.41666293e-001 + <_> + 1.57822981e-001 -2.30762124e-001 2.05089986e-001 1.27922738e+000 + 6.01053908e-002 4.06502038e-002 + <_> + -7.89328039e-001 6.23930618e-003 1.23795569e+000 9.99164581e-001 + 5.29922783e-001 -8.17155302e-001 + <_> + -6.67955279e-001 3.21924299e-001 5.69021344e-001 5.31171322e-001 + 1.01528919e+000 -5.94702780e-001 + <_> + 1.03791428e+000 -4.52953987e-002 2.17936349e+000 5.09631455e-001 + 3.86843026e-001 -5.38661301e-001 + <_> + 1.33164752e+000 2.42890090e-001 1.16095197e+000 -1.02844425e-001 + -4.00508523e-001 -1.05721310e-001 + <_> + 1.30449104e+000 3.01063985e-001 9.19727087e-001 -1.74527749e-001 + -4.47007865e-001 1.90547109e-002 + <_> + 1.27123821e+000 2.52807409e-001 1.01878691e+000 -1.90263405e-001 + -3.64564121e-001 2.86064278e-002 + <_> + -1.24655426e+000 -7.03214062e-003 -9.29565448e-003 + -4.82197076e-001 -2.91322857e-001 -1.74003378e-001 + <_> + -1.19390404e+000 -2.64974870e-002 -3.95204946e-002 + -5.52975833e-001 -3.95919412e-001 -1.51025787e-001 + <_> + -8.44110269e-003 -4.54390973e-001 1.07066083e+000 -2.95232713e-001 + -4.31476295e-001 1.22031718e-001 + <_> + -1.53645068e-001 -4.26386923e-001 9.04465854e-001 -2.47526735e-001 + -6.11795485e-001 1.48942694e-001 + <_> + 7.63535798e-002 -5.04079282e-001 1.82169378e+000 -3.67971361e-001 + -6.31464958e-001 2.53105938e-001 + <_> + -8.90194952e-001 -3.91871393e-001 -1.40351698e-001 + -7.26709604e-001 -4.08064365e-001 -2.53109336e-001 + <_> + 1.09444404e+000 8.91720176e-001 1.55862224e+000 1.04878526e-002 + -1.53654814e-001 -5.74872971e-001 + <_> + 1.36600876e+000 8.15023661e-001 1.96761346e+000 9.31494776e-003 + -2.10169718e-001 -5.38176239e-001 + <_> + 5.05314946e-001 1.46391809e-001 -1.70064867e+000 -1.50862843e-001 + -3.26608062e-001 -4.41436708e-001 + <_> + -1.28748110e-002 -3.52844834e-001 -1.50964296e+000 + -4.25113812e-002 3.33526731e-001 -3.05692643e-001 + <_> + -1.61404058e-001 -3.75818014e-001 -1.08981395e+000 5.27002454e-001 + -2.02216625e-001 -1.53131008e-001 + <_> + -1.91331595e-001 -4.34293747e-001 -9.49603617e-001 2.71827340e-001 + -2.06342444e-001 -2.18562469e-001 + <_> + -2.61716723e-001 -2.37586066e-001 -1.30374503e+000 6.06884122e-001 + 1.41187772e-001 -2.82945424e-001 + <_> + -8.15972984e-002 -2.45065585e-001 -1.14369965e+000 + -2.81709164e-001 1.02390325e+000 -9.54873502e-001 + <_> + 4.55435693e-001 1.22743651e-001 5.22416294e-001 -1.33702770e-001 + -1.10015941e+000 5.41753292e-001 + <_> + 1.84425235e-001 1.24047868e-001 6.78580403e-002 -7.58063018e-001 + 4.32705402e-001 -3.13546956e-001 + <_> + 1.11269042e-001 1.50114194e-001 3.85865271e-001 -7.17631221e-001 + 2.42686585e-001 -2.55743474e-001 + <_> + 1.03292632e+000 -9.92333591e-002 -2.74810410e+000 1.36336052e+000 + -2.31802011e+000 2.01706028e+000 + <_> + 2.11419702e+000 1.07891762e+000 -2.00277805e+000 2.29717895e-001 + -1.90353915e-001 3.52923483e-001 + <_> + 1.42305002e-001 -1.60787499e+000 -2.23066068e+000 -8.10463652e-002 + -2.51007509e+000 2.52804255e+000 + <_> + 1.78942788e+000 -8.69729340e-001 -5.35648286e-001 2.42240715e+000 + -2.85428882e+000 3.11019278e+000 + <_> + 6.91488013e-002 -1.65930378e+000 -2.73053122e+000 3.09874773e+000 + -2.61040926e+000 2.68650198e+000 + <_> + 4.31007631e-002 -1.71735060e+000 -2.48643494e+000 2.77521896e+000 + -3.28956652e+000 2.97787237e+000 + <_> + 2.14523315e+000 2.05257773e+000 -1.55997157e+000 -1.58537972e+000 + -3.39894748e+000 2.57053208e+000 + <_> + 1.62427223e+000 -1.31589592e+000 -2.31417131e+000 -2.01709032e+000 + -2.27760053e+000 3.67450684e-001 + <_> + -9.04647186e-002 -5.37088364e-002 -2.89699793e-001 + -6.59790188e-002 1.31596613e+000 -8.60924721e-001 + <_> + -5.16697653e-002 -5.87180704e-002 -1.66755289e-001 + -4.93059829e-002 1.21553612e+000 -7.92691946e-001 + <_> + -6.77846139e-003 -4.53689694e-002 3.38326901e-001 -1.06192470e+000 + 8.74827564e-001 -9.71272290e-001 + <_> + 1.53474277e-003 -1.08660825e-001 2.59242326e-001 -1.62639177e+000 + 7.50534415e-001 -9.08706307e-001 + <_> + -6.05371818e-002 -6.64816499e-002 2.80260444e-001 -1.65024948e+000 + 8.30900669e-001 -8.21880937e-001 + <_> + 6.19440265e-002 -4.22340423e-001 3.75391930e-001 -8.32016230e-001 + 4.70973581e-001 -5.15410125e-001 + <_> + 1.04064263e-001 -4.06999230e-001 4.99403030e-001 -1.16360164e+000 + 7.15413034e-001 -6.48458481e-001 + <_> + 8.52210000e-002 -3.65785450e-001 5.14666617e-001 -1.08408582e+000 + 5.93418598e-001 -5.82153261e-001 + <_> + 4.80886884e-002 -5.38843513e-001 3.66170913e-001 -1.60848153e+000 + 1.37864804e+000 -1.16651344e+000 + <_> + 7.46909380e-002 -3.33567500e-001 8.61641586e-001 -1.49334157e+000 + 1.19034481e+000 -9.67090726e-001 + <_> + -2.95012202e-002 3.60594317e-003 -5.79984426e-001 2.46745086e+000 + 1.58400223e-001 2.03216765e-002 + <_> + -1.05428487e-001 -1.51705118e-002 -7.74092376e-001 2.46467304e+000 + 2.36254379e-001 -3.19006033e-002 + <_> + 1.16811179e-001 -7.88097754e-002 -2.91043496e+000 9.81906474e-001 + -5.44274390e-001 9.94998664e-002 + <_> + -2.35114470e-001 -1.93194523e-001 -3.14918399e+000 1.01249719e+000 + 4.22789931e-001 -3.54755878e-001 + <_> + 4.28833455e-001 6.61150157e-001 1.91855621e+000 -3.65771919e-001 + 2.17921674e-001 -3.81547987e-001 + <_> + 6.81554914e-001 6.64804041e-001 2.25083184e+000 -6.27850711e-001 + 1.98251456e-001 -4.16806430e-001 + <_> + -1.89711619e-002 2.76566327e-001 8.51211131e-001 -3.76807481e-001 + -2.34486297e-001 -5.68291768e-002 + <_> + -1.78584680e-001 1.24187239e-001 -8.32082629e-001 7.60499611e-002 + 1.74608365e-001 1.57943562e-001 + <_> + 3.85604769e-001 -5.73590882e-002 -3.57203901e-001 5.82410514e-001 + 2.67369077e-002 8.39947164e-002 + <_> + 6.12278163e-001 -4.45436314e-003 -3.10408384e-001 7.22646415e-001 + 3.67886692e-001 -2.48370141e-001 + <_> + -8.21515173e-002 2.42727138e-002 -1.38220572e+000 1.07870877e+000 + 3.61432761e-001 1.94080427e-001 + <_> + 1.95624626e+000 -2.71178985e+000 2.11791635e+000 -2.46492654e-001 + 3.18167627e-001 -5.05619943e-001 + <_> + -2.04078510e-001 3.22223067e-001 -1.95645630e+000 3.33061397e-001 + 6.39808238e-001 2.30222762e-001 + <_> + -2.24030197e-001 1.69225186e-001 -2.13595057e+000 -3.79167706e-001 + 9.23224568e-001 -1.78668633e-001 + <_> + -3.02728534e-001 1.02974221e-001 -1.68855870e+000 8.19083631e-001 + 2.76766509e-001 6.15181565e-001 + <_> + -5.83203286e-002 6.59270957e-002 -2.06382298e+000 3.68232965e-001 + -5.30895352e-001 8.40377390e-001 + <_> + 2.53701925e-001 -5.50605178e-001 -2.06999071e-002 -1.45969999e+000 + 2.02671576e+000 -2.27539515e+000 + <_> + 2.60352492e-001 -3.76145601e-001 5.97941518e-001 -3.21126819e-001 + 9.74999726e-001 -3.42074931e-001 + <_> + 5.01989663e-001 -4.64066327e-001 1.14520276e+000 -9.52583075e-001 + 1.86479819e+000 -1.31178629e+000 + <_> + 4.71507907e-001 -4.42268640e-001 1.19841468e+000 -1.48544526e+000 + 1.65927970e+000 -1.31404817e+000 + <_> + 1.59485623e-001 -2.64709324e-001 7.62796044e-001 -5.40663712e-002 + 6.63726687e-001 -3.80363584e-001 + <_> + 7.85704330e-002 -3.01486492e-001 6.19719088e-001 2.69078827e+000 + 5.46207666e-001 9.01814476e-002 + <_> + 1.64473549e-001 -4.70534682e-001 4.74482715e-001 2.71062684e+000 + 8.21403623e-001 -5.25386333e-002 + <_> + 1.96617931e-001 -4.56181139e-001 5.39182186e-001 2.76569533e+000 + 7.67288983e-001 -7.38115162e-002 + <_> + 1.67244613e-001 -8.11168730e-001 5.63447773e-001 -1.30235292e-002 + 1.73287487e+000 -1.58881867e+000 + <_> + 6.13898151e-002 -2.82956094e-001 2.22334459e-001 4.49945927e-002 + -5.95636740e-002 1.72245856e-002 + <_> + 4.45459843e-001 2.44658604e-001 1.89688295e-001 1.67436600e+000 + -1.33890259e+000 1.41288888e+000 + <_> + 4.87580091e-001 2.48997957e-001 1.97335571e-001 1.83122194e+000 + -1.42924976e+000 1.41662717e+000 + <_> + 5.49097776e-001 1.39696002e-001 -4.44402367e-001 9.35369611e-001 + -8.10567737e-002 1.57529995e-001 + <_> + 1.46853828e+000 -4.55442816e-001 7.84556985e-001 -6.90390587e-001 + 1.00742352e+000 -1.17276692e+000 + <_> + 5.11965513e-001 -5.93797863e-002 -5.11192262e-001 2.70395398e+000 + 6.40909553e-001 3.36976677e-001 + <_> + 4.28833455e-001 -1.07955381e-001 -5.75351298e-001 2.71595502e+000 + 5.09711206e-001 4.15271729e-001 + <_> + 1.14487743e+000 -1.60274351e+000 -7.07419157e-001 -2.31712237e-001 + -3.47982144e+000 2.92342710e+000 + <_> + 1.86923468e+000 -1.57905221e+000 9.94140446e-001 5.24489105e-001 + -2.91727948e+000 2.49577713e+000 + + <_> + 577 + -4.3342865475707426e-002 + + 1. 5.9573933790191924e-001 1. 1. 1. 1. 1. 1. + 8.5027377489575739e-001 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1. 1. 1. 1. 1. 1. 6.9211989282923825e-002 1. + 7.4059869323433491e-001 9.4808334755900295e-002 + 1.4949556534792116e-002 1. 8.6262369894746316e-001 + 5.5773184118030061e-001 1. 1. 1. 1. 1.0944351782904316e-001 + 3.7824791933076207e-001 1. 1. 6.4478201387384981e-001 1. 1. 1. + 1. 1. 1. 1. 4.7910422680443310e-001 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 7.4137868358483328e-001 + 4.1793012081751879e-001 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 8.2989549392442352e-001 1. 2.8591189729495459e-001 + 3.6076806654121892e-002 1. 1. 8.2206036228228441e-001 + 8.9605803256840533e-001 1. 4.0661577898794887e-001 1. + 7.7944458775049741e-002 3.6462313578626759e-001 + 5.0282494580202264e-001 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 7.5325624059912344e-001 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1. 1. 1. 1. 2.2666797082782972e-001 1. 2.7045275838053725e-003 + 1. 1. 1. 2.0941726745104075e-001 1. 9.4676912588757489e-001 1. + 2.3464830299846151e-001 5.6499597552009861e-001 + 6.3712537431651528e-001 7.5519871718975828e-001 + 6.6840295321697918e-001 1.9262577103429177e-001 1. 1. 1. 1. + 7.0542366005688861e-002 1. 1. 1. 3.8036697228595961e-001 1. 1. + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 2.9444996354845759e-001 7.0427794948595046e-001 + 1.6933107869371841e-002 1. 1.3960325398083281e-001 + 6.6489905738980581e-001 6.8509034026510554e-002 1. 1. 1. 1. 1. + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1.4256259709882405e-001 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 5.5574636203527039e-001 2.7809995538430049e-001 1. 1. + 5.0034345722738105e-001 1. 1. 1. 1. 1. 1. 1. + 3.6109237799423965e-001 1. 1. 1. 1. 1. 1. 1. 1. + 8.6251914236183591e-001 1. 4.0375784297066625e-001 1. 1. 1. 1. + 1. 1. 1.3933819891603449e-001 1. 1. 1. 1. + 2.3121856357892426e-001 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. + 1. 1. 1. 6.3102598280586208e-001 1. 9.7052025699554678e-001 + 5.9371614594997912e-001 -1. -1. -1. -1. -1. + -9.4435311880395834e-001 -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -6.6653093938426289e-001 -1. -1. -1. -8.3557627731239215e-001 + -1. -1. -5.2124069528539407e-001 -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1.7109897233152596e-001 -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -9.8642596900722163e-002 -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -5.1624636089103515e-001 -1. -1. + -6.7804082980824340e-002 -1. -3.2064360643430725e-002 + -5.0419406346606188e-001 -6.3119853591083686e-002 -1. -1. -1. + -1. -1. -2.2595316048510358e-001 -1. -2.5025653975361134e-001 + -1. -1. -2.0227925907901109e-001 -1.7132369150400256e-001 + -2.6489651531955035e-002 -1. -1. -1. -1. -1. -1. -1. -1. -1. + -2.8279659157973525e-001 -1. -1. -1. -1. + -7.8805684831585254e-001 -1. -2.2772275020010688e-001 -1. -1. + -6.2765973283791310e-001 -9.3060849215898489e-001 + -3.2753002405932746e-001 -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -7.8166107926898354e-001 -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -8.2416145338913205e-001 -6.5456725360804613e-001 -1. -1. -1. + -1. -4.7932828901288471e-001 -1. -1. -1. + -7.2862677668840281e-001 -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -3.8066542005854881e-001 + -5.1968857333799445e-001 -1. -1. -1. -1. -1. -1. -1. -1. + -7.3269419354000290e-002 -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -3.2923757523865671e-001 -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. + -1. -1. -1. -7.2527821319504515e-001 -1. -1. -1. -1. -1. -1. + -5.4342260095058292e-001 -7.9984470417574327e-001 + -4.5544257714716541e-002 -8.0882970343590668e-002 + -3.0498343736024958e-001 -2.6560378113655619e-001 + -9.7764767795691365e-001 -4.7658166646375760e-001 -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -1. -1. -2.3121559035246891e-001 + -4.7935600086854169e-001 -1. -1. -1. -1. -1. -1. -1. + -2.4281667270708636e-002 -1. -1. -8.5723946397741879e-001 -1. + -1. -1. -1. -1. -1. -1. -5.4373030948213807e-001 + -2.7540505629014896e-002 -6.8039085415166012e-001 -1. -1. + -8.2229825163121972e-001 -1. -7.8154192754113339e-001 -1. + -5.1833027558406664e-001 -2.9460731341686014e-001 + -2.4709406333090239e-001 + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 + 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 + 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 + 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 + 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 + 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 + 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 + 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 + 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 + 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 + 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 + 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 + 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 + 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 + 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 + 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 + 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 + 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 + 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 + 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 + 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 + 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 + 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 + 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 + 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 + 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 + 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 + 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 + 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 + 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 + 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 + 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 + 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 + 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 + 571 572 573 574 575 576
+
diff --git a/acii/acii-client/trainedClassifiers/SVM_UD_N00074.xml b/acii/acii-client/trainedClassifiers/SVM_UD_N00074.xml new file mode 100755 index 0000000..4a21c2e --- /dev/null +++ b/acii/acii-client/trainedClassifiers/SVM_UD_N00074.xml @@ -0,0 +1,1060 @@ + + + + C_SVC + RBF + 5.0000000000000000e-001 + 1. + 2.2204460492503131e-016 + 100000 + 6 + 6 + 2 + + 1 + 2 +
i
+ + -1 1
+ 301 + + <_> + -2.05480433e+000 1.09494664e-001 3.00965995e-001 5.94358668e-002 + 1.05114365e+000 9.13799882e-001 + <_> + -1.86936057e+000 -2.52341814e-002 1.38115451e-001 -8.86653736e-002 + 1.23317790e+000 1.42801329e-001 + <_> + -1.82059184e-001 2.67446697e-001 -7.86389232e-001 -4.36784625e-001 + 1.68412876e+000 -2.08679152e+000 + <_> + 1.16186246e-001 3.74722362e-001 -3.60109657e-001 -1.13479823e-001 + 1.54366887e+000 -1.38345277e+000 + <_> + -7.28009567e-002 3.48289669e-001 -2.93609142e-001 9.59359258e-002 + 6.35145128e-001 -7.30607927e-001 + <_> + 7.88271964e-001 3.89078200e-001 6.85084879e-001 -1.52667686e-001 + 1.54285920e+000 -1.63398135e+000 + <_> + 1.95915222e-001 4.02446896e-001 2.58717425e-002 3.40694822e-002 + 1.33255947e+000 -1.23089123e+000 + <_> + -6.98480308e-002 3.62483799e-001 -2.72413045e-001 2.32945368e-001 + 7.31872082e-001 -7.12622881e-001 + <_> + -2.61788160e-001 2.45592259e-002 3.43115926e-001 1.40474901e-001 + 1.23057449e+000 9.38822508e-001 + <_> + -6.30313218e-001 -1.46643668e-001 -2.68144220e-001 5.07480681e-001 + -7.86064625e-001 2.19762135e+000 + <_> + 8.96939576e-001 -3.54617536e-001 3.79933894e-001 -5.63894331e-001 + -1.07336736e+000 1.18254483e+000 + <_> + 8.46149266e-001 -3.10259581e-001 7.15880021e-002 -1.41648245e+000 + -2.10688815e-001 1.72888353e-001 + <_> + 9.59541619e-001 -2.00442746e-001 2.25537524e-001 -6.02076650e-001 + -4.44721043e-001 9.74953234e-001 + <_> + -1.05848742e+000 1.07874835e+000 1.16006029e+000 -5.73631883e-001 + 2.92798090e+000 7.44972646e-001 + <_> + -4.38373089e-001 1.02338696e+000 5.08695722e-001 3.67451310e-001 + 1.62664294e+000 1.04443085e+000 + <_> + -1.47805259e-001 8.68156075e-001 -1.68507814e-001 -1.99246788e+000 + 2.34103608e+000 -4.11294222e+000 + <_> + -3.06082040e-001 3.68911892e-001 -1.76851675e-001 -1.49856257e+000 + 2.88967299e+000 -4.09112358e+000 + <_> + -4.22427297e-001 2.88574576e-001 -6.24397278e-001 -1.28934038e+000 + 2.46368456e+000 -3.86464357e+000 + <_> + -3.44129317e-002 5.17885722e-002 3.57381254e-001 -4.02336329e-001 + -1.04506874e+000 4.92363006e-001 + <_> + -7.21263349e-001 9.58149195e-001 -2.17989135e+000 -4.84941870e-001 + -3.73101979e-001 3.27482462e-001 + <_> + -2.57063478e-001 8.86244178e-002 6.00634277e-001 6.79939315e-002 + -9.45575014e-002 -2.16914639e-001 + <_> + -2.37574175e-001 3.02979946e-002 8.25242817e-001 -1.34765312e-001 + -1.27141142e+000 1.08432817e+000 + <_> + -1.83830947e-001 1.27794504e-001 4.61649835e-001 1.92687303e-001 + -2.22185031e-001 -9.27906036e-002 + <_> + -8.19300473e-001 2.09200072e+000 -1.74543488e+000 -1.81603268e-001 + 1.88888967e+000 -1.39905787e+000 + <_> + 3.31159204e-001 1.31067634e+000 1.59756088e+000 3.11393640e-003 + 2.29384214e-001 -1.40904343e+000 + <_> + 9.70172167e-001 1.66342422e-001 1.36532450e+000 -1.17501950e+000 + -2.31586218e-001 -1.44537091e+000 + <_> + -1.13408232e+000 8.46771955e-001 -2.16042852e+000 -1.03300560e+000 + 1.84485459e+000 -2.41410828e+000 + <_> + 2.95133501e-001 5.60264647e-001 -6.22795761e-001 -1.41604173e+000 + 8.08593512e-001 -2.41321898e+000 + <_> + 4.35442887e-002 -2.45544836e-001 1.05827057e+000 -6.36105716e-001 + 9.49052945e-002 -7.11282372e-001 + <_> + -4.75579947e-001 -1.66412115e-001 2.60269493e-001 -2.42406178e+000 + 1.57143819e+000 -3.16163349e+000 + <_> + -1.63751051e-001 -1.77758545e-001 5.77750564e-001 -1.93948281e+000 + 2.22103882e+000 -3.59572339e+000 + <_> + -1.77925095e-001 -1.96431130e-001 6.82339489e-001 -6.60115123e-001 + 3.47658992e-001 -1.29167449e+000 + <_> + -8.04785639e-002 -5.27345181e-001 8.98609519e-001 -8.73559415e-001 + 1.90845802e-001 -1.37467754e+000 + <_> + 4.85892475e-001 -2.90743768e-001 1.60707283e+000 -2.02363896e+000 + 9.90423024e-001 -2.09619188e+000 + <_> + -3.85470279e-002 -3.73014472e-002 1.35403061e+000 -6.00251079e-001 + 8.23509336e-001 -2.42194676e+000 + <_> + -1.89146206e-001 -4.42836247e-002 9.66498911e-001 -6.13904774e-001 + 2.19452453e+000 -3.29678893e+000 + <_> + -4.49594200e-001 -5.59236169e-001 7.49049962e-001 -8.24833870e-001 + 1.88667983e-001 -1.87044024e+000 + <_> + -3.19074899e-001 -3.80951554e-001 8.36224377e-001 -6.67764604e-001 + 1.71064329e+000 -2.15545630e+000 + <_> + 8.29612911e-001 7.85933137e-001 -5.56868553e-001 -1.33556795e+000 + -5.28708756e-001 -1.34237742e+000 + <_> + -7.86227703e-001 1.09130847e+000 -1.12632334e+000 2.89246082e-001 + 1.54005265e+000 6.64349556e-001 + <_> + -1.11754584e+000 1.93375587e+000 -1.63506711e+000 1.47185028e-001 + 4.86580312e-001 -3.35597306e-001 + <_> + -1.45772290e+000 2.17808270e+000 -1.50895917e+000 -1.21023691e+000 + 8.99299681e-001 -4.31799918e-001 + <_> + -1.33133769e+000 2.09692907e+000 -1.27290606e+000 -7.19343364e-001 + 9.90809500e-001 5.06284893e-001 + <_> + -8.52963805e-001 1.23700964e+000 -4.47653532e+000 -7.11313367e-001 + 1.08453727e+000 -3.80701900e-001 + <_> + 5.76251984e-001 6.94450855e-001 -1.60429442e+000 -6.97674811e-001 + 3.13291937e-001 7.32993409e-002 + <_> + -1.57879281e+000 1.27379572e+000 -1.02249336e+000 -1.32630718e+000 + -4.91613984e-001 -1.04764998e+000 + <_> + -1.80912089e+000 7.49530315e-001 -9.13926363e-001 -1.54931211e+000 + -1.48179561e-001 -1.21587968e+000 + <_> + -1.45772290e+000 1.74661994e+000 -1.55942070e+000 -1.14226818e+000 + 1.89450121e+000 -1.01401401e+000 + <_> + -2.25087857e+000 2.11665106e+000 4.04553652e-001 -1.03461349e+000 + 1.43627441e+000 -7.69527495e-001 + <_> + -1.99574578e+000 2.36162925e+000 -2.39223346e-001 -2.73308963e-001 + -2.95769513e-001 6.08281434e-001 + <_> + -2.13807678e+000 2.16222405e+000 -2.60429764e+000 -5.27362585e-001 + 1.33550577e-002 1.46033674e-001 + <_> + -1.72407675e+000 8.04183602e-001 1.93636441e+000 -1.34660745e+000 + 6.80949509e-001 -9.82076466e-001 + <_> + -1.90656745e+000 4.09261179e+000 -3.60483795e-001 -1.34861410e-001 + 1.01682138e+000 -5.95333278e-001 + <_> + -1.38508093e+000 3.12861490e+000 4.96645600e-001 -6.14495039e-001 + 1.72713375e+000 -1.18180048e+000 + <_> + -1.03958869e+000 3.13125706e+000 -4.05233771e-001 -7.16736972e-001 + 1.18113744e+000 -6.32495284e-001 + <_> + -9.95885372e-001 3.19687343e+000 -7.95434639e-002 -2.47742563e-001 + 1.90441835e+000 -1.21237171e+000 + <_> + -1.40811372e+000 2.65300179e+000 1.02159798e+000 -8.30861092e-001 + 3.69996488e-001 -4.37607169e-001 + <_> + -1.92723799e+000 2.33184958e+000 -1.46088243e+000 -2.75693238e-001 + 9.10343230e-001 -7.80139029e-001 + <_> + -7.70281911e-001 1.69354403e+000 1.59158826e+000 -1.59060574e+000 + 2.27619362e+000 -1.66952026e+000 + <_> + -3.07263196e-001 1.62043417e+000 9.59877670e-001 -1.83869445e+000 + 2.16021943e+000 -1.89022040e+000 + <_> + -1.72609821e-001 -2.71022350e-001 1.73515642e+000 8.71114805e-003 + 1.17909284e-002 9.06234384e-001 + <_> + -7.24602118e-003 -4.74916995e-001 1.35134983e+000 -1.02036929e+000 + 1.66463423e+000 -2.13691920e-001 + <_> + -1.19707007e-002 -3.44180584e-001 1.08714843e+000 -7.74296403e-001 + 6.69455707e-001 -7.48464346e-001 + <_> + -6.45327643e-002 -3.27556312e-001 1.59093952e+000 -1.35979746e-002 + 1.90991902e+000 9.59118843e-001 + <_> + -1.04017925e+000 7.95768917e-001 -2.69505191e+000 -1.03558385e+000 + 6.22926414e-001 -7.76250780e-001 + <_> + -6.12595677e-001 6.86641395e-001 -2.01120830e+000 -2.07541800e+000 + -1.79567978e-001 3.75551078e-003 + <_> + -2.15131938e-001 2.79836923e-001 -1.27975523e-001 2.90812794e-002 + 1.99208021e-001 -5.51264644e-001 + <_> + -3.94079208e-001 1.79942250e-001 -1.04542740e-001 1.11032613e-001 + -3.77204595e-003 -1.77461416e-001 + <_> + -2.80686885e-001 4.03247654e-001 -1.33043015e+000 -4.92278427e-001 + 3.79043460e-001 -1.99565065e+000 + <_> + -2.19266042e-001 7.87570924e-002 -1.35707402e+000 -6.81682289e-001 + 1.55405843e+000 -1.14398348e+000 + <_> + -3.00176173e-001 2.49670818e-001 -1.22338390e+000 -1.23201832e-001 + -5.45511663e-001 -1.01209462e+000 + <_> + -2.06484413e+000 1.05799043e+000 1.03033565e-001 -1.65865195e+000 + 6.07745826e-001 -2.29316044e+000 + <_> + -1.63667011e+000 5.35747349e-001 2.04354525e+000 3.08257312e-001 + 1.25720441e+000 -1.45291090e+000 + <_> + -1.58647037e+000 4.46079910e-001 2.42987132e+000 -2.34397545e-001 + -1.68266684e-001 2.16949284e-001 + <_> + -2.36308956e+000 -3.40723991e-001 -1.27789795e-001 + -1.11299253e+000 2.29543304e+000 -2.33317685e+000 + <_> + -2.21249056e+000 -3.70162874e-001 -4.51660603e-001 + -1.71669781e+000 1.92224050e+000 -1.41466093e+000 + <_> + -1.69690979e+000 2.11248088e+000 1.32240212e+000 2.15636224e-001 + 1.00655496e+000 -6.47841275e-001 + <_> + -1.05848742e+000 1.91804886e+000 1.67638874e+000 6.67655289e-001 + 1.58603954e+000 -1.23609018e+000 + <_> + -4.92706895e-001 1.89836597e+000 2.36104512e+000 1.23552650e-001 + 1.79579771e+000 -1.26043332e+000 + <_> + -1.11754584e+000 2.08568692e+000 -3.08859557e-001 5.77391267e-001 + 1.69670677e+000 -1.36030519e+000 + <_> + -3.96441549e-001 1.99234867e+000 1.70811427e+000 8.50281864e-002 + 2.91527176e+000 -2.25999284e+000 + <_> + -2.35802427e-001 1.96448338e+000 1.54173231e+000 -1.72396675e-001 + 3.08593345e+000 -2.48959613e+000 + <_> + -8.12804043e-001 2.14133191e+000 4.77325529e-001 -1.55266086e-002 + 2.86721945e+000 -2.15140843e+000 + <_> + -1.28468144e+000 1.73989999e+000 -2.27132827e-001 -5.95740378e-001 + 2.98674321e+000 -2.38980794e+000 + <_> + -7.13585734e-001 4.45583537e-002 7.78620392e-002 2.84921706e-001 + -6.76191568e-001 1.55138564e+000 + <_> + -3.42698306e-001 2.69907676e-002 4.00957704e-001 1.51052758e-001 + 6.40481263e-002 3.33754569e-001 + <_> + -4.22427297e-001 1.94282085e-001 1.20041579e-001 1.73903823e-001 + 2.90257454e-001 2.20337451e-001 + <_> + 6.15821123e-001 2.66476005e-001 1.85998410e-001 -7.72765353e-002 + -1.35870740e-001 8.38614106e-001 + <_> + -6.21704273e-002 1.44160241e-001 -9.39334810e-001 6.03121370e-002 + 1.97773278e-001 -4.87042934e-001 + <_> + -9.16996747e-002 1.20635085e-001 -8.76736999e-001 2.02883646e-001 + 1.01961292e-001 -4.06728625e-001 + <_> + -6.03986718e-002 1.66936710e-001 -9.18555915e-001 3.44451219e-002 + 5.74939847e-001 -7.29417264e-001 + <_> + 1.19164157e+000 3.04509580e-001 7.99804807e-001 -9.15427923e-001 + 9.26762462e-001 -1.05108094e+000 + <_> + -1.31518710e-002 3.52903813e-001 -5.42627454e-001 9.05346274e-002 + 5.16780615e-001 -6.31441891e-001 + <_> + 1.90009370e-001 2.16247737e-001 -1.66674867e-001 1.89191982e-001 + 2.30319560e-001 -4.26979065e-001 + <_> + 1.30089974e+000 2.72744410e-002 1.62614262e+000 -7.43955493e-001 + 1.80579677e-001 -2.36449495e-001 + <_> + -7.04726934e-001 -7.24247396e-002 -3.38203013e-001 2.81952739e-001 + -6.11492656e-002 8.21798623e-001 + <_> + -4.90344554e-001 -1.04598947e-001 -1.20980136e-001 4.41774875e-001 + -4.04095113e-001 8.78969014e-001 + <_> + -1.01478410e+000 -4.25550975e-002 -4.70786870e-001 3.76172245e-001 + -4.10641164e-001 1.26471388e+000 + <_> + 2.97495842e-001 -5.65892220e-001 -5.36479950e-001 -9.95850682e-001 + 3.89384061e-001 -2.00253412e-001 + <_> + 4.51638520e-001 -2.48211637e-001 1.42989874e-001 -9.64899659e-001 + 4.13525462e-001 -2.73053408e-001 + <_> + 3.84311855e-001 -2.30401009e-001 -6.41429484e-001 -7.80315340e-001 + 2.49093547e-001 -2.45238796e-001 + <_> + -9.97066557e-001 -2.25630194e-001 -1.29949868e+000 + -3.95831198e-001 -4.53977674e-001 6.78628922e-001 + <_> + 2.64173243e-002 4.95981127e-001 2.08196715e-002 2.81311035e-001 + -4.18982118e-001 3.36809456e-001 + <_> + 1.08297384e+000 8.99574280e-001 -5.00162601e-001 -6.85004890e-001 + -8.11977446e-001 1.20426226e+000 + <_> + 1.26841760e+000 8.54691029e-001 6.30236089e-001 -2.11380675e-001 + -6.62394464e-001 1.66454017e+000 + <_> + -4.23017859e-001 2.80139327e-001 -4.37414140e-001 3.66136879e-001 + -3.65743220e-001 7.99587846e-001 + <_> + 8.69977474e-003 -1.51305765e-001 -7.01050401e-001 -3.66930425e-001 + -5.07159948e-001 2.99047828e-001 + <_> + 1.11250317e+000 -8.13834891e-002 2.15597749e+000 -2.79479653e-001 + -5.97233951e-001 2.75902241e-001 + <_> + -9.53953862e-001 2.80114204e-001 -2.93689442e+000 -4.97003406e-001 + -9.13349271e-001 1.08210659e+000 + <_> + -2.49976456e-001 1.13325380e-001 -4.51278389e-001 -2.50862632e-003 + 6.62817657e-001 -6.37072802e-001 + <_> + -2.92498589e-001 1.27381101e-001 -3.10146123e-001 3.24436903e-001 + 2.43853971e-001 -3.82608294e-001 + <_> + 1.39952743e+000 3.80893260e-001 3.76806289e-001 -2.95479059e-001 + 5.46972871e-001 -1.00810945e-001 + <_> + -1.86168301e+000 9.62965250e-001 -1.43390119e-001 1.93308413e-001 + 4.68787640e-001 -1.22812891e+000 + <_> + -1.40634191e+000 -6.46857142e-001 1.01555002e+000 -1.04586315e+000 + 7.37247169e-001 1.18851316e+000 + <_> + -6.65157735e-001 7.98242033e-001 -9.29970801e-001 5.07552862e-001 + -1.21470690e+000 1.28524494e+000 + <_> + -9.58087921e-001 9.61973071e-001 -1.57439923e+000 3.56072664e-001 + -9.88772418e-003 2.44619465e+000 + <_> + -3.22618425e-001 4.56893712e-001 -1.44301844e+000 -2.78325200e-001 + -1.11131525e+000 1.15785432e+000 + <_> + -2.38164768e-001 6.27789199e-001 -7.24795401e-001 4.02723402e-001 + -5.73712111e-001 5.34142554e-001 + <_> + -5.48221886e-001 4.10380125e-001 -1.69567585e+000 3.95722926e-001 + 4.70592320e-001 1.78318322e+000 + <_> + -2.27095842e+000 -8.95782173e-001 1.47514570e+000 -6.96432173e-001 + -2.39588004e-002 4.30619240e-001 + <_> + -1.27405095e+000 6.79339468e-001 -6.15264773e-001 -2.19356894e+000 + -5.11968613e-001 2.48973086e-001 + <_> + -1.86463594e+000 8.98678482e-001 1.97518319e-001 -1.65024161e+000 + -1.45296901e-001 9.93018508e-001 + <_> + -8.65956664e-001 2.49616122e+000 2.96690607e+000 -7.27611601e-001 + 1.14295411e+000 4.76732790e-001 + <_> + -1.95204246e+000 4.74452615e-001 -2.17215037e+000 -2.77380228e-001 + -3.46001625e-001 1.45744193e+000 + <_> + -5.49403071e-001 1.31947744e+000 2.80470681e+000 -1.11524284e+000 + 4.85254496e-001 9.59313512e-001 + <_> + -3.11987877e-001 1.47213519e+000 3.07171297e+000 -7.98705578e-001 + 8.24355423e-001 5.43444097e-001 + <_> + -1.17247033e+000 2.08707142e+000 2.52685928e+000 -7.34945059e-001 + 1.93115520e+000 8.90191078e-001 + <_> + -3.03129107e-001 1.39232910e+000 3.11116600e+000 6.40819728e-001 + 7.30722547e-002 2.00366426e+000 + <_> + -3.13169062e-001 2.00045395e+000 8.50139797e-001 -1.19980597e+000 + 6.31274879e-001 5.43133855e-001 + <_> + -2.97223270e-001 1.14956610e-001 2.40215898e+000 -1.58218801e+000 + 5.92854381e-001 -1.45959660e-001 + <_> + -1.37765303e-001 3.52345139e-001 2.33526096e-001 -1.09679505e-001 + 6.59316182e-001 -8.60451102e-001 + <_> + -8.57938305e-002 -2.28210658e-001 7.77651444e-002 -1.59911227e+000 + -9.80070174e-001 -3.52496058e-001 + <_> + -2.40265894e+000 1.07249701e+000 -9.43345189e-001 -1.46551490e+000 + -4.85256910e-001 6.02908790e-001 + <_> + -8.52032453e-002 3.97998765e-002 5.51607370e-001 -2.69406646e-001 + 3.77600044e-002 -5.51971972e-001 + <_> + -1.25363022e-001 4.48547989e-001 1.56075907e+000 3.39212358e-001 + 3.35309595e-001 -5.67163050e-001 + <_> + -1.63160458e-001 3.73780906e-001 1.44475520e+000 3.60381693e-001 + 3.44233394e-001 -5.25578380e-001 + <_> + -1.66029346e+000 8.09785783e-001 -4.21848804e-001 3.30412954e-001 + 8.71370077e-001 -8.67150545e-001 + <_> + -1.48134625e+000 9.85604644e-001 -3.93608212e+000 -1.67774546e+000 + -6.26129434e-002 -7.36931562e-002 + <_> + -1.85577714e+000 2.75944531e-001 2.15114617e+000 -9.07284737e-001 + 1.96047354e+000 -1.22374976e+000 + <_> + -2.46939492e+000 1.94431469e-001 -2.01544738e+000 -8.34777474e-001 + 8.64712119e-001 3.76414001e-001 + <_> + -1.99220228e+000 -1.70773423e+000 1.50185144e+000 -5.98999321e-001 + 4.49579537e-001 4.10751879e-001 + <_> + -2.37194848e+000 -6.87036589e-002 1.76362514e+000 -1.11913157e+000 + 5.75264812e-001 7.63446391e-001 + <_> + -2.21367168e+000 -1.80352688e+000 1.63374627e+000 -2.35130519e-001 + 5.51391363e-001 -2.09232792e-001 + <_> + -2.44636202e+000 -1.79629576e+000 -6.09124377e-002 + -1.21676818e-001 7.86347091e-001 -1.33937746e-001 + <_> + -1.27168858e+000 1.58624589e+000 1.85485303e+000 -1.22617745e+000 + -4.00441855e-001 2.97969043e-001 + <_> + -2.04501420e-001 1.45099998e+000 2.85125756e+000 -7.40009427e-001 + 6.34797096e-001 -5.66099644e-001 + <_> + -1.98595569e-001 1.15192568e+000 1.22486782e+000 7.43705928e-001 + 1.77512407e+000 -1.44382644e+000 + <_> + -1.51678133e+000 1.34085786e+000 -6.93077981e-001 -4.57736284e-001 + 2.09995222e+000 3.50222766e-001 + <_> + -2.26682425e+000 1.27868485e+000 -3.11752272e+000 -2.29359940e-001 + 6.04896069e-001 1.06935382e+000 + <_> + -1.98216236e+000 1.50425184e+000 1.82900321e+000 -3.22403222e-001 + 3.57669163e+000 1.55820751e+000 + <_> + -2.54912376e+000 -2.98807740e+000 1.78620195e+000 1.02103949e+000 + 1.13111043e+000 1.54870033e+000 + <_> + -2.32883573e+000 1.47677147e+000 -2.04391885e+000 -1.04647386e+000 + 7.47696757e-001 -5.71998119e-001 + <_> + -1.43528056e+000 -4.43141133e-001 -1.29173887e+000 + -6.62862599e-001 -3.20893914e-001 4.17356879e-001 + <_> + -2.47234774e+000 1.79976046e-001 8.18521976e-001 -1.74721330e-001 + 3.63984019e-001 -2.65560895e-001 + <_> + 1.07327469e-001 1.43084121e+000 2.11616921e+000 -8.55198383e-001 + -1.02699304e+000 9.19318795e-001 + <_> + 8.78381655e-002 -1.02137707e-001 4.85822797e-001 -1.67068485e-002 + 2.34557316e-001 -5.70067227e-001 + <_> + 1.64107668e+000 -3.75549719e-002 3.16240638e-001 9.04348716e-002 + 1.02447875e-001 1.06644453e-002 + <_> + 1.56843472e+000 1.06116176e-001 -3.24139565e-001 -1.10661695e-002 + -9.97291598e-003 -5.78261316e-001 + <_> + -3.20846677e-001 -2.26929799e-001 1.68688953e-001 5.17266870e-001 + -1.02835751e+000 1.35555983e+000 + <_> + -2.95451522e-001 -1.05007321e-001 1.54119506e-001 4.41893280e-001 + -1.01230443e+000 1.05843377e+000 + <_> + -2.96882521e-002 -3.91291678e-001 -2.37465754e-001 + -1.36281967e-001 -3.45163226e-001 5.04659891e-001 + <_> + 9.13475990e-001 1.54332712e-001 1.43166614e+000 -2.06320778e-001 + 3.14381629e-001 -6.27728403e-001 + <_> + -3.42698306e-001 7.99187347e-002 -3.65115970e-001 -1.79242834e-001 + 2.54624993e-001 -4.84229028e-001 + <_> + -3.11397314e-001 2.39245310e-001 -3.05807322e-001 -4.53643769e-001 + 8.88817251e-001 -1.78647566e+000 + <_> + -1.21853590e+000 2.32962027e-001 -1.78889185e-001 1.29906988e+000 + 5.12518167e-001 -6.41345263e-001 + <_> + 1.39834630e+000 5.50013161e+000 -5.65061688e-001 4.60314870e-001 + 5.67450784e-002 -2.34164596e+000 + <_> + -4.19474363e-001 -7.83572137e-001 8.37298334e-001 -2.55813658e-001 + -4.40484807e-002 -9.40302983e-002 + <_> + 2.08908096e-001 -7.58576989e-001 1.95141673e-001 1.67035615e+000 + -1.95803416e+000 3.51297474e+000 + <_> + 1.94734052e-001 -4.88534629e-001 4.10348594e-001 9.93804276e-001 + -2.60090041e+000 3.16658831e+000 + <_> + 2.77415961e-001 -1.10303879e+000 -2.40232676e-001 1.63204777e+000 + -2.32991382e-001 2.71901536e+000 + <_> + -9.46526006e-002 -8.99635255e-001 -5.26735522e-002 1.64323485e+000 + -4.01692957e-001 2.94801521e+000 + <_> + 4.54591453e-001 -3.88968408e-001 1.80191267e+000 1.53996527e+000 + -4.73549098e-001 2.51140761e+000 + <_> + -1.94461465e-001 -3.37041542e-002 -3.78291190e-001 4.41562563e-001 + 1.05856109e+000 -6.77008390e-001 + <_> + -3.78133416e-001 2.67641954e-002 -4.45779532e-001 6.20547056e-001 + 1.26940417e+000 -8.70283186e-001 + <_> + 4.03801143e-001 -2.17270777e-001 -4.16777909e-001 1.62675411e-001 + 1.02825832e+000 -8.75964940e-001 + <_> + 1.14414491e-001 -5.12077883e-002 -1.00960624e+000 1.42913967e-001 + 1.10370779e+000 -1.23303032e+000 + <_> + 3.53601426e-001 -1.44183338e-001 -1.14340401e+000 2.48407334e-001 + 2.83301086e-003 1.49503499e-001 + <_> + -4.29309579e-003 -7.54638314e-002 -1.49141997e-001 2.00849593e-001 + -1.84910035e+000 1.51966119e+000 + <_> + 5.47313333e-001 -2.09852791e+000 1.50015306e+000 9.01659727e-001 + -2.18317103e+000 1.77507520e+000 + <_> + 6.06712550e-002 -1.26561141e+000 1.15463936e+000 3.53268981e-001 + -2.62931585e+000 2.07329869e+000 + <_> + -3.14600058e-002 -5.01429617e-001 -1.21693218e+000 1.73127484e+000 + -9.15021449e-002 1.42459345e+000 + <_> + 5.93378961e-001 -1.25165665e+000 -1.80271947e+000 1.26234472e+000 + -2.12977195e+000 -5.10351658e-001 + <_> + 9.49501693e-001 -5.00779152e-001 2.07492352e+000 1.67525470e+000 + -3.94207627e-001 2.51263213e+000 + <_> + 1.16565573e+000 -6.97103679e-001 4.80582327e-001 1.39744803e-001 + -6.25403643e-001 1.09267020e+000 + <_> + 1.76628077e+000 -5.31284630e-001 1.92090142e+000 1.10204899e+000 + -5.39511859e-001 1.43882132e+000 + <_> + 1.45124912e-001 1.37848198e-001 -6.26851976e-001 6.55505896e-001 + -1.05923879e+000 3.17454386e+000 + <_> + -1.30087703e-001 1.97580636e-001 -5.63118398e-001 2.11474800e+000 + -1.25572515e+000 1.93244410e+000 + <_> + -7.81162232e-002 3.57515186e-001 -8.06522131e-001 2.10939050e+000 + -1.33498955e+000 8.56664240e-001 + <_> + -5.51765382e-001 1.24106482e-002 -1.68218303e+000 2.05582595e+000 + -2.11364508e+000 3.59361076e+000 + <_> + 1.94143474e-001 1.15312457e-001 -6.78567708e-001 2.99287289e-001 + -1.47006011e+000 1.99873281e+000 + <_> + -2.00957909e-001 3.76471460e-001 -1.19533241e+000 2.02873185e-001 + -8.78029823e-001 5.23504138e-001 + <_> + -1.07915783e+000 -9.90356028e-001 3.95146608e-001 1.52996027e+000 + 2.66775876e-001 1.10116768e+000 + <_> + 8.62095058e-001 -9.10753727e-001 8.37675154e-001 -2.35568896e-001 + -1.74370134e+000 -7.34748483e-001 + <_> + 8.78631473e-001 -1.74827099e+000 7.62793064e-001 -3.85390997e-001 + -1.38764024e+000 -1.22492278e+000 + <_> + 1.17805803e+000 -8.30004454e-001 2.77404451e+000 9.06403542e-001 + -6.92191899e-001 3.22371125e-001 + <_> + 1.96648908e+000 -5.31485200e-001 9.53652933e-002 1.45150149e+000 + -9.44550812e-001 -1.55117452e-001 + <_> + -1.98004976e-001 -7.00841665e-001 -1.38557795e-002 + -8.00187945e-001 -1.33813667e+000 5.37317812e-001 + <_> + 2.59698391e-001 -4.55626369e-001 9.08084735e-002 -1.04841316e+000 + -1.21735334e+000 2.04228029e-001 + <_> + 1.91628933e+000 -1.14460301e+000 -7.25344479e-001 1.79197621e+000 + -1.35096979e+000 1.16387129e+000 + <_> + -4.43097770e-001 -4.12713736e-001 -3.03541034e-001 5.78845859e-001 + -3.96684885e-001 1.40040839e+000 + <_> + -3.33839536e-001 -4.16663229e-001 -3.23832750e-001 5.05937278e-001 + -2.43016362e-001 8.71206045e-001 + <_> + 1.93636918e+000 -1.10629988e+000 7.34625816e-001 1.70739639e+000 + -1.06918526e+000 1.94197416e+000 + <_> + 1.95408678e+000 1.64903045e-001 -7.80376256e-001 3.91883343e-001 + -6.67714357e-001 3.98567647e-001 + <_> + 1.48398113e+000 -3.98284435e+000 4.34315242e-002 2.95723557e-001 + -1.34557652e+000 7.18850553e-001 + <_> + 1.48339057e+000 -3.48831296e+000 1.04115576e-001 9.89399076e-001 + -1.85798550e+000 1.21306908e+000 + <_> + 1.66470015e+000 -2.37357783e+000 6.12668276e-001 -1.42826945e-001 + -1.29535890e+000 7.63857245e-001 + <_> + 1.62985563e+000 -2.22368050e+000 -1.74587893e+000 1.16956842e+000 + -2.92480409e-001 -1.39330328e-001 + <_> + 1.11427486e+000 -1.47863889e+000 1.45155680e+000 1.86915803e+000 + -1.49564111e+000 1.37625563e+000 + <_> + 4.41598594e-001 -1.94093490e+000 -3.86766940e-001 1.85813463e+000 + -9.12156880e-001 1.29439449e+000 + <_> + 9.61063579e-002 4.69498158e-001 -1.48431253e+000 1.57018736e-001 + -7.37728894e-001 -1.37306547e+000 + <_> + -1.38355896e-001 3.56468678e-001 -1.23631954e+000 8.70081782e-001 + -2.06788018e-001 -5.85470080e-001 + <_> + 3.48876745e-001 -1.81622314e+000 -8.57476175e-001 1.80990398e+000 + 7.29050189e-002 -9.21148732e-002 + <_> + 3.80177736e-001 -2.07509890e-001 1.31934440e+000 1.51908946e+000 + -1.81549370e+000 1.76194906e+000 + <_> + 1.23888826e+000 -5.92253208e-001 -1.51375020e+000 1.90478706e+000 + -1.37438774e+000 1.99391937e+000 + <_> + 1.96471727e+000 -3.85851204e-001 3.96831542e-001 3.05422604e-001 + -1.99259686e+000 1.77606344e+000 + <_> + 1.49815512e+000 -3.36303085e-001 2.85277295e+000 5.63210964e-001 + 9.79793817e-002 -1.74726442e-001 + <_> + 2.00133348e+000 5.77494681e-001 -7.17890561e-002 1.41455686e+000 + -2.49751997e+000 2.22631693e+000 + <_> + 1.98184431e+000 4.62721556e-001 1.03258228e+000 1.32733166e+000 + -1.46761107e+000 1.44310236e+000 + <_> + 1.53772438e+000 -1.94111991e+000 -1.06848419e+000 -8.52823675e-001 + -2.49836192e-001 -7.29921609e-002 + <_> + 1.05344462e+000 -1.98922050e+000 -1.38039911e+000 -3.39753151e-001 + -2.81109452e+000 2.36570764e+000 + <_> + 1.01800954e+000 -1.70010984e+000 2.70398818e-002 5.24545133e-001 + -2.50206947e+000 2.29617596e+000 + <_> + 1.74738204e+000 -1.78640139e+000 2.23677826e+000 -4.95320380e-001 + -5.53042471e-001 6.89218700e-001 + <_> + 9.30602968e-001 -2.37806439e+000 -7.60940433e-001 1.23574340e+000 + -2.76916295e-001 -1.67034417e-001 + <_> + 1.84246624e+000 -2.17505980e+000 -5.10366380e-001 5.14263511e-001 + 3.75950992e-001 -2.52729923e-001 + <_> + 9.43595827e-001 -1.12715900e+000 -5.24393879e-002 -4.47663635e-001 + 1.23166966e+000 -8.13028574e-001 + <_> + 4.84120697e-001 -1.26254424e-001 2.00974286e-001 -2.52328217e-001 + 5.89873970e-001 -1.17759359e+000 + <_> + 5.30186355e-001 -7.15449452e-002 6.26882434e-001 -3.04810762e-001 + 7.16721594e-001 -1.52275479e+000 + <_> + -6.20863914e-001 -1.48675786e-002 3.62826288e-001 -4.56633121e-002 + 6.03452504e-001 -8.62175465e-001 + <_> + -3.39154810e-001 -1.50100999e-002 6.26042664e-001 -4.16947939e-002 + 3.21796745e-001 -9.49132204e-001 + <_> + -5.44087827e-001 -1.60249427e-001 5.28197065e-002 -3.55755091e-002 + 2.87045658e-001 -7.27929294e-001 + <_> + 3.37065041e-001 -7.07185939e-002 2.96123862e-001 -2.02598780e-001 + -2.11351424e-001 -3.73620093e-001 + <_> + -3.68684053e-001 -2.28334904e-001 3.14986348e-001 4.17022616e-001 + -6.71763957e-001 8.56596470e-001 + <_> + -1.21228926e-001 -1.32286459e-001 4.73767281e-001 4.59987879e-001 + -3.34117532e-001 5.22737682e-001 + <_> + -1.54631054e+000 -1.22382142e-001 -1.02056360e+000 5.57160079e-001 + -5.17269850e-001 6.22269392e-001 + <_> + -1.74061310e+000 -2.42240801e-001 -1.75855625e+000 1.78072244e-001 + -4.46251392e-001 6.09070361e-001 + <_> + -1.67446756e+000 -5.98234795e-002 -1.68682599e+000 1.98179603e-001 + -1.21143460e-001 3.64467502e-001 + <_> + -3.12578470e-001 4.90736157e-001 3.32591891e-001 1.52511001e+000 + -8.45979869e-001 5.53045928e-001 + <_> + -1.87965035e-001 2.71460146e-001 6.22804701e-001 1.10398340e+000 + -9.31952417e-001 6.19227827e-001 + <_> + 2.70919532e-001 1.01758309e-001 -2.87024602e-002 -3.23681980e-001 + 2.51210809e-001 -1.78496039e+000 + <_> + 3.78996581e-001 4.61347699e-002 1.42303526e-001 -6.56234771e-002 + 1.58201102e-002 -1.60882976e-002 + <_> + 5.75661361e-001 7.74543062e-002 2.76526570e-001 -4.17772353e-001 + 4.94289994e-001 -1.20895374e+000 + <_> + -4.08843845e-001 5.68823695e-001 -4.33925867e-001 -4.43451963e-002 + 1.61792845e-001 -1.51099056e-001 + <_> + -4.76170510e-001 5.43449879e-001 -4.08506721e-001 8.11825693e-001 + 3.16163003e-002 1.64628252e-001 + <_> + -3.36792469e-001 2.87019193e-001 -4.26106870e-001 -1.01129390e-001 + -3.89845408e-002 -1.02885246e-001 + <_> + 8.95758450e-001 5.58739424e-001 9.93613780e-001 7.48041153e-001 + 5.31124651e-001 -1.25358772e+000 + <_> + -3.15531403e-001 -8.71119082e-001 1.32001197e+000 -5.16603172e-001 + 1.16776145e+000 -1.06972206e+000 + <_> + -3.31477195e-001 -5.23591220e-001 1.86216331e+000 9.66917872e-001 + 1.17849950e-002 -1.56512689e-002 + <_> + 3.09307545e-001 -6.62476480e-001 2.35376191e+000 1.01213479e+000 + 1.08314788e+000 -1.12617803e+000 + <_> + 9.25287664e-001 4.64554429e-002 1.96210396e+000 1.50170887e+000 + -3.29967111e-001 -7.37554014e-001 + <_> + -1.49236266e-002 1.58025935e-001 -3.39037389e-001 6.19965434e-001 + -2.16726139e-002 -7.24065661e-001 + <_> + -1.31268874e-001 -6.61411703e-001 -1.47480583e+000 1.21452737e+000 + -2.54638863e+000 2.31295943e+000 + <_> + -2.58244663e-001 1.52511001e-001 5.83820045e-001 8.23412538e-001 + 3.65869224e-001 -1.83160767e-001 + <_> + 1.91190541e-001 8.84311870e-002 -4.23361480e-001 -2.58750259e-003 + 8.88978541e-002 -8.40291008e-002 + <_> + -5.11605620e-001 1.20184220e-001 4.02107000e-001 6.32384419e-001 + 5.72534084e-001 -2.67032385e-001 + <_> + 1.72882408e-001 -9.26714540e-002 -7.52650440e-001 5.72351292e-002 + 2.20762208e-001 -3.35971527e-002 + <_> + 3.54782581e-001 -3.04266840e-001 2.31995583e+000 9.39051688e-001 + 9.65466082e-001 -9.02118444e-001 + <_> + -1.59000891e-004 -1.81038365e-001 -1.30058795e-001 2.81367183e-001 + -6.83272004e-001 4.98556465e-001 + <_> + 3.01629931e-001 -4.84494539e-003 3.70782554e-001 5.10218680e-001 + -9.52622056e-001 8.31259847e-001 + <_> + -9.30330455e-001 -3.70547503e-001 5.09718537e-001 1.26718175e+000 + -1.09471190e+000 1.67242360e+000 + <_> + 5.21918178e-001 -1.03190470e+000 2.62339759e+000 2.21406400e-001 + -3.63243908e-001 -8.91587064e-002 + <_> + 1.00383544e+000 -5.10730028e-001 -2.06512734e-001 1.60875607e+000 + -2.92997551e+000 2.94572687e+000 + <_> + 7.30735436e-002 -1.44726023e-001 -1.87683439e+000 9.56262887e-001 + -2.01401997e+000 2.26609015e+000 + <_> + -6.93505824e-001 -6.37903452e-001 -7.94811130e-001 3.48116726e-001 + 1.64869502e-001 -1.45520389e-001 + <_> + 2.17555618e+000 1.20807827e+000 -2.91262090e-001 4.53830473e-002 + -1.60499871e+000 1.38797832e+000 + <_> + 1.42551315e+000 8.51034343e-001 -1.28795719e+000 1.49711299e+000 + 2.24158585e-001 -8.04263115e-001 + <_> + 1.39362156e+000 5.53814232e-001 -1.60748398e+000 8.82202506e-001 + -1.10709679e+000 6.56508088e-001 + <_> + 1.20404387e+000 2.99621820e-001 -1.37778831e+000 1.45416701e+000 + -1.52974904e+000 -4.03036028e-001 + <_> + 8.75678539e-001 -1.05970144e+000 1.19992507e+000 1.85406152e-002 + -1.15471208e+000 -1.86972499e+000 + <_> + 4.90026563e-001 -6.44605577e-001 3.45769823e-001 -4.35509622e-001 + 9.17681217e-001 -2.02473974e+000 + <_> + 1.89384711e+000 4.83544970e+000 -1.17496526e+000 -1.47913456e-001 + 6.20515049e-001 -2.70597386e+000 + <_> + 1.58201826e+000 -8.88989985e-001 -1.36014235e+000 1.37572539e+000 + -1.89223778e+000 -3.81466120e-001 + <_> + 1.70840335e+000 -6.31558478e-001 -1.57867348e+000 1.19229031e+000 + 3.57111134e-002 -1.09446573e+000 + <_> + 9.21744168e-001 -2.14154482e+000 4.55079734e-001 -1.39856413e-001 + 2.74448276e-001 -1.64267230e+000 + <_> + 1.81057465e+000 -1.12356603e+000 2.33144617e+000 3.40333223e-001 + 1.05048454e+000 -9.15143728e-001 + <_> + 1.48811519e+000 -1.49552536e+000 -1.22728932e+000 5.27849257e-001 + -1.35430753e+000 -8.74630511e-001 + <_> + 1.49815512e+000 -1.22424090e+000 -1.99380457e+000 -1.17092168e+000 + 6.50964260e-001 -1.92608058e+000 + <_> + 1.23121071e+000 -1.61496282e+000 -2.26016474e+000 1.09994400e+000 + 8.62189889e-001 -9.46554124e-001 + <_> + 1.51030764e-001 -2.76456445e-001 -8.99609923e-001 6.76882789e-002 + -6.87711596e-001 4.86269027e-001 + <_> + 1.37447312e-001 -3.15797836e-001 -2.30274248e+000 1.17989883e-001 + -4.05411512e-001 5.86130440e-001 + <_> + 2.00133348e+000 -1.18170333e+000 -3.42280746e-001 -2.08741173e-001 + -1.33880723e+000 1.12435651e+000 + <_> + 1.88321662e+000 -6.23959184e-001 5.29027820e-001 -1.52003646e-001 + -7.46629000e-001 6.07045054e-001 + <_> + -2.73599863e-001 2.54672319e-001 -2.27110314e+000 9.98922288e-001 + -8.94938409e-001 1.00147486e+000 + <_> + -2.47023538e-001 3.81461531e-001 -2.21778321e+000 6.98437750e-001 + 3.69777158e-002 3.42529535e-001 + <_> + -1.85012117e-001 7.23093987e-001 -1.59740674e+000 9.71983612e-001 + -1.92643714e+000 -5.02873473e-002 + <_> + -1.46033496e-001 5.23089409e-001 -1.95292234e+000 1.20641959e+000 + -2.25787818e-001 -1.34156322e+000 + <_> + -4.79963869e-002 3.30955505e-001 -1.26378703e+000 1.04560256e+000 + -4.29173291e-001 8.78102481e-001 + <_> + 1.27196109e+000 -1.39066887e+000 2.08360386e+000 1.88445246e+000 + -8.26702192e-002 2.82305986e-001 + <_> + 1.36940765e+000 -1.69719315e+000 1.61165118e+000 1.99007893e+000 + -3.76358449e-001 5.44515431e-001 + <_> + 6.47712708e-001 -1.69283092e-001 6.82396889e-002 -1.44537359e-001 + 2.98198760e-001 -2.49575227e-002 + <_> + 6.05781198e-001 -3.46655071e-001 4.70873833e-001 -2.93220818e-001 + 2.12582514e-001 -1.79416403e-001 + <_> + 1.84955323e+000 -9.11023542e-002 -1.07277453e+000 3.34288418e-001 + 1.40164196e-001 -1.35068566e-001 + <_> + 1.10659730e+000 -1.46264815e+000 -2.54773164e+000 9.35707986e-001 + -4.91066426e-001 5.63785076e-001 + <_> + 1.50760448e+000 -2.11799765e+000 -1.59352016e+000 7.58100986e-001 + -2.24193358e+000 1.60087216e+000 + <_> + 1.23121071e+000 -1.68282223e+000 -2.14924240e+000 7.46896267e-001 + -1.63750255e+000 1.24567902e+000 + <_> + 1.45858598e+000 -1.62890387e+000 -1.15966105e+000 -9.90285158e-001 + -1.52704370e+000 1.38834417e+000 + <_> + 1.72907388e+000 -1.10160089e+000 2.57799888e+000 -1.29306927e-001 + -4.46669161e-001 -7.72185504e-001 + <_> + 1.81529927e+000 -1.30143869e+000 2.45233774e+000 4.39763427e-001 + -1.04556596e+000 -6.75419271e-001 + <_> + 1.66588128e+000 -1.52893388e+000 1.11113834e+000 4.23758119e-001 + -2.01446295e+000 -4.05643135e-001 + <_> + 1.82002401e+000 -1.55104709e+000 2.78022432e+000 5.14035821e-001 + -3.13116193e-001 -7.79701531e-001 + <_> + 2.11472583e+000 1.80801943e-001 2.13833690e+000 9.16068614e-001 + 2.56109953e-001 -2.34540775e-001 + <_> + 2.19150186e+000 5.67225814e-001 2.06994414e+000 4.88722235e-001 + -6.66286349e-001 4.32554662e-001 + + <_> + 301 + -9.1037802742970811e-002 + + 6.2775610035353174e-002 3.0797509135582751e-001 + 5.0959775991987222e-001 5.3167048608517409e-001 1. 1. 1. 1. + 3.6413625438757063e-001 1. 1. 8.6685035365713387e-003 1. + 4.0847958237993509e-001 9.2165657157587094e-002 + 3.9835768957082718e-001 3.6945412635117048e-001 + 8.3235918306200446e-002 1. 7.3377328099934941e-002 1. 1. 1. + 3.5383052500017326e-001 3.3848838204948384e-001 + 2.9028361026375421e-001 5.4498372058399303e-001 + 5.6272652323676464e-001 8.2665395263542063e-001 + 4.2128489114290085e-001 4.8716503445181226e-002 1. 1. + 2.8486296526680854e-001 3.7672317418107765e-001 + 2.5150933793861474e-001 8.0457937853177319e-001 + 5.9311151122149253e-001 5.2492994936508008e-001 + 2.5785754823199009e-001 1.8532490604949978e-001 + 1.0035447730840705e-001 1.2541614437888119e-002 + 7.3548226230231006e-001 1.4388859328579526e-001 + 1.8479490750114469e-001 2.3944602367241979e-002 + 1.2840978415045623e-001 2.8555495182115093e-001 + 4.9002070625624689e-001 4.2874317335774648e-001 + 6.0301793569491176e-002 7.0778085115444511e-001 + 9.4647425632348930e-002 1.0407776650931741e-002 + 3.3555839345801747e-001 2.9361259139912388e-001 + 1.5890528275393754e-002 2.6255048129573927e-001 + 2.6258218635157859e-001 6.9820914201146678e-001 + 5.5721832644418612e-001 9.4429721228532021e-002 + 5.3271453157671855e-001 7.8026163910197427e-002 + 2.7869745146391028e-001 1. 1. 6.1640103035247729e-001 + 3.2315168425646246e-002 1. 5.6710723092520154e-001 + 3.7099832894088902e-001 1.7722272512557391e-001 + 4.9451659634497286e-001 4.5525384896907373e-001 + 7.1466785050164183e-002 1.6965416419360732e-001 + 2.2766642382244481e-001 9.6437431653888989e-002 + 9.0875789808955762e-002 5.3665232274912344e-001 + 2.8591413258879025e-002 5.0430195777856868e-001 1. 1. + 6.8697155095704343e-002 3.0109619981060898e-001 1. 1. 1. + 5.9690501243105754e-001 4.8837034462037904e-001 1. + 9.9284081986587480e-001 1. 1. 5.2675036072682380e-001 1. + 5.4092164064313164e-001 4.4209218131612260e-001 + 5.8227230303334276e-001 1. 3.6304633278846593e-001 + 5.3433207387970594e-001 9.3835459697401336e-001 1. 1. + 7.9735863570098087e-001 1. 1. 1. 1.9857679682452195e-001 + 1.2805358222006621e-001 1. 7.9487704688598573e-002 + 6.0222661991998061e-001 1. 6.9093916434926450e-001 + 6.8092437630455749e-002 1.2809295071261972e-001 + 1.9493316962385210e-001 4.5077914450757300e-001 + 5.6762007652034918e-001 1.8600610909639101e-001 + 2.4729737922189982e-002 3.6750933960312177e-001 + 8.1934794524049859e-001 1.6035594974877104e-001 + 1.6320218069918329e-002 1. 7.1134599444114466e-001 + 2.7640288936595392e-001 1. 7.9647677213191126e-001 1. + 5.0612815107155562e-001 5.4416130788098460e-001 + 4.8834111683039555e-001 6.1834738407642320e-001 + 2.3025510493980242e-001 4.1503291602252429e-001 + 5.2329836926540763e-001 7.4185622271743634e-001 + 2.0971173914229641e-001 1.4050867677994050e-001 + 1.7522767562129460e-001 2.6319355890769164e-001 + 4.1223227899360160e-001 7.5259581058218716e-001 + 8.7571442536058264e-001 1.1133957983725502e-001 1. + 2.4785703519248187e-001 2.3063769855230210e-001 -1. + -1.6781332928513421e-001 -1.8167268324397242e-002 -1. -1. -1. + -1. -1. -1. -4.0618919433852241e-001 -7.8473066761217369e-001 + -1. -3.0914116740136682e-001 -1.6190552107402842e-001 + -1.7980597762177244e-001 -4.0923677445897666e-001 + -2.7058223795861730e-001 -1. -1. -6.3132494136502315e-001 -1. + -6.2374701419358913e-001 -1.6719274112462629e-001 + -5.3400067330741485e-001 -5.7505065129915667e-001 + -1.1588912774390983e-002 -5.6004255330583186e-001 + -6.4763690535142560e-001 -2.7465062093430963e-002 + -4.7048733059656594e-002 -6.6473496140752764e-001 + -1.5494153592191823e-001 -1.7768588887827630e-001 + -8.5927821568794571e-001 -1. -1. -4.7489035208971225e-001 + -2.3319475897157996e-002 -2.3535270216219414e-001 + -8.7324662804586922e-002 -2.9517736191465867e-002 -1. -1. + -2.6710872475481784e-001 -1. -1.5880428399306395e-001 + -3.0094681331569229e-001 -1.6229367283315893e-001 + -7.1699778036143413e-001 -5.1170456788686158e-001 + -1.3502774967992071e-001 -2.9474927338143897e-001 + -7.3545988297587267e-002 -2.3308363863613840e-001 -1. + -2.9764244325749778e-001 -3.0523723743582409e-001 + -1.6246969440134030e-002 -3.1690153754159062e-001 + -3.2227663622679587e-001 -1.8257615914724787e-001 + -5.2448699738379578e-001 -3.2716982002493517e-001 + -6.0856132870711910e-001 -7.3475534272915355e-001 + -2.5857745097693976e-002 -8.1047259650061620e-001 + -3.3009344742254421e-002 -3.3041283195187127e-001 + -1.1443736456003321e-003 -3.1585130068599504e-001 -1. -1. -1. + -1. -1. -1. -1. -1. -1. -1. -2.2586828812756454e-001 + -1.3215205865384297e-001 -1. -1. -1. -1. -1. -1. + -9.2849704631110863e-001 -1. -7.1796319146084431e-001 + -1.2121608382243573e-002 -1.5140329394769128e-001 -1. + -2.3921212950289958e-001 -8.5572860214108293e-001 -1. -1. -1. + -6.5486084428674596e-001 -1.3032069098889779e-001 + -5.0590009824848059e-001 -2.4352388613878614e-001 + -6.8023074064502664e-001 -4.2844758792505466e-001 + -1.4674539573094780e-001 -4.1808636152797879e-001 -1. + -6.9187080831813774e-001 -7.4532165704071660e-002 + -6.1324754479821384e-003 -6.1133748679295941e-001 + -9.4074094506536299e-001 -7.8466521377393827e-001 + -2.9067615463333363e-001 -5.9303379158106552e-002 + -3.7995145510496375e-001 -5.3532449430649620e-001 + -1.5246078989067294e-001 -1. -7.0050171321570720e-001 + -3.2078401645569415e-001 -5.8536732123444302e-001 + -1.9510569345373447e-002 -8.7280606570187391e-001 + -3.0189027556005815e-001 -4.3108987304459162e-001 + -6.3241972756082443e-001 -5.3324757213647045e-001 + -3.4420274235039117e-001 -4.9714180917415196e-002 + -5.6246292934222819e-001 -1. -1. -5.5574138642010135e-002 + -3.0430116131894658e-001 -3.9355504124170393e-001 + -1.3158041452822788e-001 -6.4525507556735018e-001 + -3.0948706123130615e-001 -4.7662697714586560e-002 + -4.9168814669595023e-001 -8.4476153106607596e-002 + -1.9614276844198239e-001 -1. + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 + 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 + 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 + 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 + 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 + 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 + 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 + 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 + 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 + 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 + 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 + 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 + 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 + 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 + 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 + 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 + 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 + 299 300
+
diff --git a/acii/install/install.sh b/acii/install/install.sh new file mode 100755 index 0000000..9fa1386 --- /dev/null +++ b/acii/install/install.sh @@ -0,0 +1,194 @@ +#!/bin/sh +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +set -ex + +OPENCV_VERSION="2.4.13.6" +PROTOBUF_VERSION="3.5.1" +LOG4CXX_VERSION="0.11.0" +ACTIVEMQ_CPP_VERSION="3.9.5" + +AMQCPP_DIR="/usr" +LOG4CXX_DIR="/usr" + +PROCS=$(nproc) + +# Default C++ standard for building acii-client (override in CI with ACII_CXX_STANDARD=11) +: "${ACII_CXX_STANDARD:=17}" + +if [ -e /sbin/apk ]; then + ALPINE=1 +elif [ -e /usr/bin/apt ]; then + DEBIAN=1 +fi + +# If protobuf is already installed, check the installed major version. +if [ -n "$(which protoc 2>/dev/null)" ]; then + if [ "$(protoc --version | awk '{print $2}' | cut -d. -f1)" -lt 3 ]; then + echo + echo "You have $(protoc --version) currently installed on your system." + echo "Protobuf version 3 or greater is required for this application." + echo "Please remove any previous versions before installing ACII." + exit 1 + fi +fi + +build_protoc() +{ + if [ -z "$(which protoc 2>/dev/null)" ]; then + OLDPATH=$PWD + if [ ! -f "protobuf-all-${PROTOBUF_VERSION}.tar.gz" ]; then + curl -Lko "protobuf-all-${PROTOBUF_VERSION}.tar.gz" \ + "https://github.com/google/protobuf/releases/download/v${PROTOBUF_VERSION}/protobuf-all-${PROTOBUF_VERSION}.tar.gz" + fi + tar xf "protobuf-all-${PROTOBUF_VERSION}.tar.gz" + cd "protobuf-${PROTOBUF_VERSION}" + ./configure + make clean + make -j"$PROCS" install + cd "$OLDPATH" + rm -rf protobuf-* + fi +} + +build_log4cxx() +{ + LOG4CXX_DIR="/usr/local" + OLDPATH=$PWD + if [ ! -f "apache-log4cxx-${LOG4CXX_VERSION}.tar.gz" ]; then + curl -Lko "apache-log4cxx-${LOG4CXX_VERSION}.tar.gz" \ + "https://archive.apache.org/dist/logging/log4cxx/${LOG4CXX_VERSION}/apache-log4cxx-${LOG4CXX_VERSION}.tar.gz" + fi + tar -zxf "apache-log4cxx-${LOG4CXX_VERSION}.tar.gz" + cd "apache-log4cxx-${LOG4CXX_VERSION}" + mkdir build && cd build + cmake -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=RELEASE -DCMAKE_INSTALL_LIBDIR="${LOG4CXX_DIR}/lib" .. + make -j1 -s install + cd "$OLDPATH" + rm -rf "apache-log4cxx-${LOG4CXX_VERSION}"* +} + +build_opencv() +{ + if [ -z "$(which opencv_version 2>/dev/null)" ]; then + OLDPATH=$PWD + if [ ! -f "opencv-${OPENCV_VERSION}.tar.gz" ]; then + curl -Lko "opencv-${OPENCV_VERSION}.tar.gz" \ + "https://github.com/opencv/opencv/archive/${OPENCV_VERSION}.tar.gz" + fi + tar -zxf "opencv-${OPENCV_VERSION}.tar.gz" + cd "opencv-${OPENCV_VERSION}" + + # CMake 4+ can't set CMP0042 to OLD; OpenCV 2.4 tries to do that. + sed -i 's/cmake_policy(SET CMP0042 OLD)/cmake_policy(SET CMP0042 NEW)/' CMakeLists.txt + + mkdir -p build + cd build + cmake -DCMAKE_BUILD_TYPE=RELEASE \ + -DBUILD_TESTS=OFF \ + -DBUILD_PERF_TESTS=OFF \ + -DBUILD_EXAMPLES=OFF \ + -DENABLE_PRECOMPILED_HEADERS=OFF \ + -DCMAKE_CXX_STANDARD=11 -DCMAKE_CXX_STANDARD_REQUIRED=ON \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ + -DBUILD_opencv_python=OFF \ + -DBUILD_opencv_java=OFF \ + .. + make -j1 -s install + cd "$OLDPATH" + rm -rf "opencv-${OPENCV_VERSION}"* + fi +} + +build_activemq_cpp() +{ + AMQCPP_DIR="/usr/local" + OLDPATH=$PWD + ACTIVEMQ_CPP_TARBALL="activemq-cpp-library-${ACTIVEMQ_CPP_VERSION}-src.tar.gz" + ACTIVEMQ_CPP_URL="https://archive.apache.org/dist/activemq/activemq-cpp/${ACTIVEMQ_CPP_VERSION}/${ACTIVEMQ_CPP_TARBALL}" + + if [ -f "${ACTIVEMQ_CPP_TARBALL}" ] && ! tar -tzf "${ACTIVEMQ_CPP_TARBALL}" >/dev/null 2>&1; then + echo "Removing invalid ${ACTIVEMQ_CPP_TARBALL}" + rm -f "${ACTIVEMQ_CPP_TARBALL}" + fi + + if [ ! -f "${ACTIVEMQ_CPP_TARBALL}" ]; then + curl -fL --retry 3 -o "${ACTIVEMQ_CPP_TARBALL}" "${ACTIVEMQ_CPP_URL}" + fi + + tar -zxf "${ACTIVEMQ_CPP_TARBALL}" + cd "activemq-cpp-library-${ACTIVEMQ_CPP_VERSION}" + ./configure + make -j"$PROCS" install + cd "$OLDPATH" + rm -rf "activemq-cpp-library-${ACTIVEMQ_CPP_VERSION}"* +} + +STARTPATH=$PWD + +# install dependencies +if [ "$ALPINE" ]; then + # Alpine protobuf (apk) depends on Abseil at runtime; protoc will fail without it + deps="protobuf abseil-cpp curl apr apr-util" + build_deps="alpine-sdk cmake protobuf-dev abseil-cpp-dev curl-dev apr-dev apr-util-dev" + + apk -v update > /tmp/apk-update.log 2>&1 || { tail -n 200 /tmp/apk-update.log; exit 15; } + apk -v add --no-cache $deps $build_deps > /tmp/apk-add.log 2>&1 || { tail -n 200 /tmp/apk-add.log; exit 15; } + + build_log4cxx +elif [ "$DEBIAN" ]; then + deps="openssl curl libapr1 cmake liblog4cxx-dev libapr1-dev" + build_deps="g++ autoconf automake libtool libcurl4-openssl-dev" + echo "/usr/local/lib" > /etc/ld.so.conf.d/usr-local.conf + echo "deb [trusted=yes] http://archive.debian.org/debian stretch main non-free contrib" > /etc/apt/sources.list + echo "deb-src [trusted=yes] http://archive.debian.org/debian stretch main non-free contrib" >> /etc/apt/sources.list + echo "deb [trusted=yes] http://archive.debian.org/debian-security stretch/updates main non-free contrib" >> /etc/apt/sources.list + apt update + apt -y install ${deps} ${build_deps} + build_protoc +fi + +# download and build activemq-cpp +build_activemq_cpp + +# download and build opencv +build_opencv + +cd "$STARTPATH" + +# build ACII +cd "$(dirname "$0")/../acii-client/" + +rm -rf build +[ "$DEBIAN" ] && ldconfig + +ACII_PROTO_DIR="$PWD/../../buffers/src/main/proto" + +echo "=== proto dir check ===" +echo "Using ACII_PROTO_DIR=${ACII_PROTO_DIR}" +ls -la "$ACII_PROTO_DIR" || true +find "$ACII_PROTO_DIR" -maxdepth 1 -name '*.proto' -print || true + +mkdir build +cd build +cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ + -DCMAKE_CXX_STANDARD="${ACII_CXX_STANDARD}" \ + -DCMAKE_CXX_STANDARD_REQUIRED=ON \ + -DACII_PROTO_DIR="${ACII_PROTO_DIR}" \ + -DLOG4CXX_DIR="${LOG4CXX_DIR}" \ + -DAMQCPP_DIR="${AMQCPP_DIR}" \ + .. +make -j"$PROCS" install + +[ "$ALPINE" ] && apk del ${build_deps} +[ "$DEBIAN" ] && apt -y remove ${build_deps} && apt -y autoremove + +cd "$STARTPATH" diff --git a/biqt/Dockerfile b/biqt/Dockerfile new file mode 100644 index 0000000..d7f4297 --- /dev/null +++ b/biqt/Dockerfile @@ -0,0 +1,16 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# +FROM ghcr.io/mitre/biqt:26.05 + +COPY target/biqt-component-*.jar /app/ +COPY install/launch.sh /app/ + +VOLUME ["/app/log", "/app/config"] +ENTRYPOINT ["/app/launch.sh"] diff --git a/biqt/README.md b/biqt/README.md new file mode 100644 index 0000000..79a236a --- /dev/null +++ b/biqt/README.md @@ -0,0 +1,49 @@ +> +> NOTICE +> +> This software (or technical data) was produced for the U. S. Government and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV (May 2014) - Alternative IV (Dec 2007). +> +> (c) 2024 The MITRE Corporation. All Rights Reserved. +> + + +# BIQT Component + +The BIQT component is an Iris Analysis Toolkit (IAT) service that analyzes an iris image and returns its quality score and whether contact lenses are present. + +## Starting the Service + +This service can be started only with Docker through the IAT interface. Follow the [Quickstart for Users](../README.md#quickstart-for-users) instructions. + +If you make changes to the scripts and want to create a new Docker image, see [Rebuild Docker Image](#rebuild-docker-image). + +## Rebuild Docker Image +### Prerequisites +##### Package Installation +To build this Docker image, complete the following prerequisites. +1. Install Homebrew by following the [environment setup instructions](../web/README.md#environment-set-up), if it is not already installed. +2. Run the following command to install Maven: + ```sh + brew install maven + ``` +3. Build and install the public BIQT Java dependency. From the main `iat/` project directory, clone the BIQT repository outside this repository, then install its Java artifact into your local Maven repository: + ```sh + git clone https://github.com/mitre/biqt.git ../biqt-core + cd ../biqt-core/java + mvn install -DskipTests + cd ../../iat + ``` + This installs `org.mitre:biqt:26.05`, which the IAT BIQT component requires. The BIQT Java tests require a separately built native `libbiqtapi` library, so this command skips those upstream tests. +4. Build the IAT BIQT JAR by running the following command in the main `iat/` project directory: + ```sh + mvn -pl biqt -am package + ``` + +### Building Docker Image +To build a new Docker image, run the following command from the `iat/biqt/` subdirectory: + ```sh + # The '--platform linux/amd64' is for building on Apple Silicon Systems + docker build --platform linux/amd64 \ + -t new_biqt_image:YOUR_TAG_HERE \ + -f Dockerfile . + ``` diff --git a/biqt/install/launch.sh b/biqt/install/launch.sh new file mode 100755 index 0000000..8d6ad89 --- /dev/null +++ b/biqt/install/launch.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +CONFIG_PATH=/app/config/application.properties +LOG_FILE="log/biqt-component.$HOSTNAME.log" +JAVA_OPTS="" +SPRING_OPTS="--logging.file=$LOG_FILE" + +. /etc/profile.d/biqt.sh + +cd /app + +if [ -f "$CONFIG_PATH" ]; then + echo "External configuration found @ $CONFIG_PATH." +else + echo "External configuration not found. You can add one by providing it at $CONFIG_PATH." +fi + +RUN_JAR=$(find -type f -name '*.jar' | head -n 1) +java $JAVA_OPTS -jar $RUN_JAR $SPRING_OPTS \ No newline at end of file diff --git a/biqt/pom.xml b/biqt/pom.xml new file mode 100644 index 0000000..d561996 --- /dev/null +++ b/biqt/pom.xml @@ -0,0 +1,68 @@ + + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.4 + + + org.mitre + biqt-component + 0.10 + A component that provides BIQT quality scores for requests published on a work queue. + + org.mitre.iwp.biqt.Main + 17 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + + org.mitre + biqt + 26.05 + + + org.mitre + buffers + 1.0 + + + + + org.springframework.boot + spring-boot-starter-activemq + + + com.fasterxml.jackson.core + jackson-databind + + + com.cedarsoftware + json-io + 4.14.0 + + + diff --git a/biqt/src/main/java/org/mitre/iwp/biqt/BiqtResult.java b/biqt/src/main/java/org/mitre/iwp/biqt/BiqtResult.java new file mode 100644 index 0000000..6c2cc4a --- /dev/null +++ b/biqt/src/main/java/org/mitre/iwp/biqt/BiqtResult.java @@ -0,0 +1,70 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.biqt; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class BiqtResult { + + private String provider; + + public String getProvider() { + return provider; + } + + private long errorCode; + + public long getErrorCode() { + return errorCode; + } + + private String message; + + public String getMessage() { + return message; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class QualityResult { + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Metrics { + private double quality; + + public double getQuality() { + return quality; + } + + private double cosmeticContactConfidence; + + @JsonProperty("cosmetic_contact_confidence") + public double getCosmeticContactConfidence() { + return cosmeticContactConfidence; + } + } + + private Metrics metrics; + + public Metrics getMetrics() { + return metrics; + } + } + + private List qualityResult; + + public List getQualityResult() { + return qualityResult; + } +} diff --git a/biqt/src/main/java/org/mitre/iwp/biqt/Main.java b/biqt/src/main/java/org/mitre/iwp/biqt/Main.java new file mode 100644 index 0000000..d6bc93a --- /dev/null +++ b/biqt/src/main/java/org/mitre/iwp/biqt/Main.java @@ -0,0 +1,44 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.biqt; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer; +import org.springframework.context.annotation.Bean; +import org.springframework.jms.annotation.EnableJms; +import org.springframework.jms.config.DefaultJmsListenerContainerFactory; +import org.springframework.jms.config.JmsListenerContainerFactory; + +import jakarta.jms.ConnectionFactory; + +@SpringBootApplication +@EnableJms +public class Main { + + // A logger for this class. + private static final Logger log = LoggerFactory.getLogger(Main.class); + + @Bean + public JmsListenerContainerFactory factory(ConnectionFactory connectionFactory, + DefaultJmsListenerContainerFactoryConfigurer configurer) { + DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); + configurer.configure(factory, connectionFactory); + return factory; + } + + public static void main(String[] args) { + log.info("Starting up!"); + SpringApplication.run(Main.class, args); + } +} diff --git a/biqt/src/main/java/org/mitre/iwp/biqt/Receiver.java b/biqt/src/main/java/org/mitre/iwp/biqt/Receiver.java new file mode 100644 index 0000000..c59a58a --- /dev/null +++ b/biqt/src/main/java/org/mitre/iwp/biqt/Receiver.java @@ -0,0 +1,507 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.biqt; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.protobuf.InvalidProtocolBufferException; + +import jakarta.annotation.PostConstruct; +import jakarta.jms.BytesMessage; +import jakarta.jms.JMSException; +import jakarta.jms.Message; +import jakarta.jms.Session; + +import org.json.simple.JSONObject; +import org.mitre.biqt.BIQT; +import org.mitre.biqt.ProviderInfo; +import org.mitre.iwp.buffers.ContactClassifier; +import org.mitre.iwp.buffers.Messages; +import org.mitre.iwp.buffers.Biqt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.jms.core.JmsTemplate; +import org.springframework.jms.core.MessageCreator; +import org.springframework.jms.support.JmsHeaders; +import org.springframework.messaging.handler.annotation.Headers; +import org.springframework.stereotype.Component; + +import java.io.*; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.ProviderNotFoundException; +import java.nio.file.StandardOpenOption; +import java.util.*; + +@Component +public class Receiver { + + // A logger for this class. + private static final Logger log = LoggerFactory.getLogger(Receiver.class); + + // The JMS template that will be used to send messages. + private JmsTemplate jmsTemplate; + + private ObjectMapper mapper = new ObjectMapper(); + + // A re-usable BIQT instance. + private BIQT biqtInstance; + + // The name of the BIQTIris provider exposed through the framework. This may + // vary slightly + // from version to version, so it's not hard-coded here. Instead, it's set in + // the post constructor. + private List availableProviderList = new ArrayList<>(); + @Value("${iwp.biqt.requestedProviders}") + private List requestedProviderList; + + @Value("${iwp.biqt.response_queue}") + private String responseBiqtQueue; + + @Value("${iwp.contactdetection.response_queue}") + private String responseContactQueue; + + @Value("${iwp.contactdetection.detection_threshold}") + private Double contactDetectionThreshold; + + @Value("${iwp.biqt.request_queue}") + private String listenerQ; + + @Value("${spring.activemq.broker-url}") + private String jmsBroker; + + private enum ProviderType { + BIQTCONTACTDETECTOR, + BIQTFACE, + BIQTIRIS, + NA + } + + @Autowired + public Receiver(JmsTemplate jmsTemplate) { + this.jmsTemplate = jmsTemplate; + this.biqtInstance = new BIQT(); + log.info("JMS Template and BIQT instance instantiated"); + } + + @PostConstruct + public void init() { + + for (ProviderInfo providerInfo : biqtInstance.getProviders()) { + String providerName = providerInfo.getName(); + availableProviderList.add(providerName); + } + + if (availableProviderList == null || availableProviderList.isEmpty()) { + throw new ProviderNotFoundException("There are no providers installed or provided by the BIQT instance."); + } else { + log.info("BIQT Installed Provider(s): {}", availableProviderList.isEmpty() ? "" : Arrays.deepToString(availableProviderList.toArray())); + availableProviderList.replaceAll(String::toLowerCase); + } + + Set availableProviderSet = new HashSet<>(availableProviderList); + + if (requestedProviderList == null || requestedProviderList.isEmpty()) { + throw new ProviderNotFoundException("There are no providers requested in the configuration properties"); + } else { + log.info("requestedProvider(s) from configuration file: {}", requestedProviderList.toArray()); + } + + ProviderType checkProviderType; + + for (String currentProvider : requestedProviderList) { + if (availableProviderSet.contains(currentProvider.toLowerCase())) { + checkProviderType = ProviderType.valueOf(currentProvider.toUpperCase()); + + switch (checkProviderType) { + case BIQTCONTACTDETECTOR: + case BIQTIRIS: + log.info("The {} provider is installed and available ", currentProvider); + break; + case BIQTFACE: + log.info("The requested {} provider is present but messaging " + + "has not yet been implemented to support it", currentProvider); + break; + default: + log.info("The {} provider is not supported", currentProvider); + throw new ProviderNotFoundException("The " + currentProvider + " provider is not supported. BIQT will terminate."); + } + } else { + // Shoudln't be in this since it should have terminated + // during initialization when we failed to find the desired provider. + log.info("The requested {} provider does not appear to be installed", currentProvider); + throw new ProviderNotFoundException("The requested " + currentProvider + " provider does not appear to be installed. BIQT will terminate."); + } + } + + log.info("The BIQT service is alive and waiting at: {}:{}", jmsBroker, listenerQ); + } + + private Messages.ImageServiceQuery unpack(BytesMessage message) { + try { + // Attempt to parse the request from the incoming message body. + int length = (int) message.getBodyLength(); + byte[] data = new byte[length]; + message.readBytes(data); + return Messages.ImageServiceQuery.parseFrom(data); + } catch (JMSException | InvalidProtocolBufferException e) { + // Failure to unpack the request. This isn't exceptional, so we can return null for now. + return null; + } + } + + @JmsListener(destination = "${iwp.biqt.request_queue}", containerFactory = "factory") + public void processRequestMessage(@Headers Map headers, Message message) { + + // Get the broker-provided message id and the user-provided ImageServiceQuery id if it's available. + // Combine them so that it's easier to trace the processing of a request. + String messageId = headers.getOrDefault(JmsHeaders.MESSAGE_ID, "NoMessageIdProvided").toString(); + Messages.ImageServiceQuery query = unpack((BytesMessage) message); + + if (query == null) { + log.warn("[{}] The request could not be unpacked.", messageId); + return; + } + + final String finalMessageId = messageId + "|" + query.getId(); + + String replyToBiqt = responseBiqtQueue; + String replyToContact = responseContactQueue; + + if (!(message instanceof BytesMessage)) { + log.warn("[{}] Expected a {}, but received a {}. This message will not be processed.", + messageId, BytesMessage.class.getSimpleName(), message.getClass().getSimpleName()); + return; + } + + new Thread() { + @Override + public void run() { + for (String currentProviderName : requestedProviderList) { + ProviderType currentProviderType = ProviderType.valueOf(currentProviderName.toUpperCase()); + + switch (currentProviderType) { + case BIQTIRIS: { + Biqt.BiqtResponse.Builder biqtReply = Biqt.BiqtResponse.newBuilder() + .setImageId(query.getId()); + try { + // Finish building the response. + processIrisRequestInternal(finalMessageId, query, biqtReply, currentProviderName); + } catch (Exception exception) { + log.error("[{}] Processing or response generation failed.", finalMessageId, exception); + + // Set the quality to a very large negative number to communicate the error. + biqtReply.setQuality(Integer.MIN_VALUE); + } + + jmsTemplate.send(replyToBiqt, new MessageCreator() { + @SuppressWarnings("null") + @Override + public Message createMessage(Session session) throws JMSException { + BytesMessage message = session.createBytesMessage(); + + message.writeBytes(biqtReply.build().toByteArray()); + log.info("Message {} Response sent to {}.", biqtReply.getImageId(), replyToBiqt); + + return message; + } + }); + + log.info("[{}] Quality: {}.", biqtReply.getImageId(), biqtReply.getQuality()); + } + break; + + case BIQTCONTACTDETECTOR: { + ContactClassifier.ContactClassifierResponse.Builder contactReply = ContactClassifier.ContactClassifierResponse + .newBuilder() + .setImageId(query.getId()); + + try { + // Finish building the response. + processContactRequestInternal(finalMessageId, query, contactReply, currentProviderName); + } catch (Exception exception) { + log.error("[{}] Processing or response generation failed.", finalMessageId, exception); + + // Set the detection code -1 to communicate the error. + contactReply.setDetectionCode("-1"); // -1 = Error + } + + jmsTemplate.send(replyToContact, new MessageCreator() { + @SuppressWarnings("null") + @Override + public Message createMessage(Session session) throws JMSException { + BytesMessage message = session.createBytesMessage(); + + message.writeBytes(contactReply.build().toByteArray()); + log.info("Message {} Response sent to {}.", contactReply.getImageId(), replyToContact); + + return message; + } + }); + + log.info("[{}] Contact Detection: {}.", contactReply.getImageId(), contactReply.getDetectionCode()); + } + break; + default: + log.warn("The {} provider is not supported", currentProviderType); + break; + } + } + } + }.start(); + } + + private File downloadToFile(String messageId, URI source) { + try { + File target = File.createTempFile("iwp-biqt", ".bin"); + target.deleteOnExit(); + + try (InputStream stream = source.toURL().openStream()) { + byte[] data = stream.readAllBytes(); + Files.write(target.toPath(), data, StandardOpenOption.APPEND); + } + return target; + } catch (IOException ioe) { + log.error("[{}] Failed to download '{}'.", messageId, source, ioe); + return null; + } + } + + private File extractImageDataToFile(String messageId, Messages.ImageServiceQuery request) { + try { + File target = File.createTempFile("iwp-biqt", ".bin"); + target.deleteOnExit(); + try (FileOutputStream fos = new FileOutputStream(target)) { + request.getImageBytes().writeTo(fos); + } + return target; + } catch (IOException ioe) { + log.error("[{}] Failed to copy the embedded image data to a file.", messageId, ioe); + return null; + } + } + + private void processIrisRequestInternal(String messageId, + Messages.ImageServiceQuery request, + Biqt.BiqtResponse.Builder response, + String providerName) throws IOException { + + List jsonObjects = new ArrayList<>(); + int quality = Integer.MIN_VALUE; + + log.info("Processing with provider: {}.", providerName); + + // Check if the path and the image bytes aren't null + if (Boolean.TRUE.equals(isIrisRequestPathOrBytesNull(messageId, request, response, providerName, jsonObjects))) { + return; + } + + if (jsonObjects.size() != 1) { + log.warn( + "[{}] BIQT returned an unexpected number of results ({}). Only the first (if available) will be considered.", + messageId, jsonObjects.size()); + } + + for (JSONObject obj : jsonObjects) { + log.debug("json objects: [{}] ", obj); + try { + BiqtResult biqtResult = mapper.readValue(obj.toJSONString(), BiqtResult.class); + + if (biqtResult.getErrorCode() != 0) { + log.warn("[{}] BIQT return error code {}.", messageId, biqtResult.getErrorCode()); + } else if (biqtResult.getQualityResult().size() != 1) { + log.warn("[{}] BIQT returned an unexpected number of quality results ({}).", + messageId, biqtResult.getQualityResult().size()); + } else if (biqtResult.getQualityResult().get(0).getMetrics() == null) { + log.warn("[{}] BIQT did not return metrics for this request.", messageId); + } else { + quality = (int) (biqtResult.getQualityResult().get(0).getMetrics().getQuality()); + } + } catch (IOException ioe) { + log.warn("[{}] Failed to make sense of BIQT's json output.", messageId, ioe); + } + break; + } + response.setQuality(quality); + log.info("setQuality {}", response.getQuality()); + } + + private Boolean isIrisRequestPathOrBytesNull(String messageId, + Messages.ImageServiceQuery request, + Biqt.BiqtResponse.Builder response, + String providerName, + List jsonObjects) throws IOException { + + if (request.getPath() != null && request.getPath().length() > 0) { + // Get the URI which is expected to start with http://, https://, file://, etc. + URI uri = URI.create(request.getPath()); + + // If it doesn't start with file://, it's considered a remote file and will need + // to be downloaded to the local host. + boolean isRemoteFile = !uri.getScheme().equalsIgnoreCase("file"); + File target = isRemoteFile ? downloadToFile(messageId, uri) : new File(uri.getPath()); + + if (target == null) { + // Couldn't download the file. + response.setQuality(Integer.MIN_VALUE); + return true; + } else { + // The file was a local file or it was successfully downloaded. Run BIQT on it. + // If it was a remote file, clean it up after BIQT is done. + try { + jsonObjects.addAll(biqtInstance.runProvider(providerName, Arrays.asList(target.getAbsolutePath()))); + } finally { + if (isRemoteFile) { + Files.delete(target.toPath()); + } + } + } + } else if (request.getImageBytes() != null && request.getImageBytes().size() > 0) { + // The image was embedded in the request. Extract it to a file, then run BIQT on it. + File target = extractImageDataToFile(messageId, request); + if (target != null) { + try { + jsonObjects.addAll(biqtInstance.runProvider(providerName, Arrays.asList(target.getAbsolutePath()))); + } finally { + Files.delete(target.toPath()); + } + } + } else { + log.warn("[{}] The request did not contain an image path or image data.", messageId); + response.setQuality(Integer.MIN_VALUE); + + return true; + } + + return false; + } + + private void processContactRequestInternal(String messageId, + Messages.ImageServiceQuery request, + ContactClassifier.ContactClassifierResponse.Builder response, + String providerName) throws IOException { + + List jsonObjects = new ArrayList<>(); + double detectionCode = -1; + String errorMessageString = "ERROR"; + + log.info("Processing with provider: {}.", providerName); + + if (Boolean.TRUE.equals(isContactRequestPathOrBytesNull(messageId, request, response, providerName, + errorMessageString, jsonObjects))) { + return; + } + + if (jsonObjects.size() != 1) { + log.warn( + "[{}] BIQT returned an unexpected number of results ({}). Only the first (if available) will be considered.", + messageId, jsonObjects.size()); + } + + for (JSONObject obj : jsonObjects) { + log.debug("json objects: [{}] ", obj); + try { + + BiqtResult biqtResult = mapper.readValue(obj.toJSONString(), BiqtResult.class); + + if (biqtResult.getErrorCode() != 0) { + log.warn("[{}] BIQT return error code {}.", messageId, biqtResult.getErrorCode()); + } else if (biqtResult.getQualityResult().size() != 1) { + log.warn("[{}] BIQT returned an unexpected number of quality results ({}).", + messageId, biqtResult.getQualityResult().size()); + } else if (biqtResult.getQualityResult().get(0).getMetrics() == null) { + log.warn("[{}] BIQT did not return metrics for this request.", messageId); + } else { + detectionCode = (biqtResult.getQualityResult().get(0).getMetrics().getCosmeticContactConfidence()); + log.info("cosmetic_contact_confidence: {} ", detectionCode); + } + } catch (IOException ioe) { + log.warn("[{}] Failed to make sense of BIQT's json output.", messageId, ioe); + response.setResponse(errorMessageString); + response.setMessage(ioe.getMessage()); + } + } + + log.info("Using contactDetectionThreshold : {} ", contactDetectionThreshold); + + if (detectionCode > contactDetectionThreshold) { + response.setDetectionCode("1"); // 1 = Cosmetic + response.setResponse("OK"); + } else if (detectionCode > 0 && contactDetectionThreshold > detectionCode) { + response.setDetectionCode("0"); // 0 = none + response.setResponse("OK"); + } else { + response.setDetectionCode("-1"); // -1 = Error + } + + log.info("contactResponse Result {}", response); + } + + private Boolean isContactRequestPathOrBytesNull(String messageId, + Messages.ImageServiceQuery request, + ContactClassifier.ContactClassifierResponse.Builder response, + String providerName, + String errorMessageString, + ListjsonObjects) throws IOException { + if (request.getPath() != null && request.getPath().length() > 0) { + // Get the URI which is expected to start with http://, https://, file://, etc. + URI uri = URI.create(request.getPath()); + + // If it doesn't start with file://, it's considered a remote file and will need + // to be downloaded to the local host. + boolean isRemoteFile = !uri.getScheme().equalsIgnoreCase("file"); + File target = isRemoteFile ? downloadToFile(messageId, uri) : new File(uri.getPath()); + + if (target == null) { + // Couldn't download the file. + response.setResponse(errorMessageString); + response.setMessage("The request did not contain an image path or image data."); + response.setDetectionCode("-1"); + + return true; + } else { + // The file was a local file or it was successfully downloaded. Run BIQT on it. + // If it was a remote file, clean it up after BIQT is done. + try { + jsonObjects.addAll(submitContactRequest(providerName, Arrays.asList(target.getAbsolutePath()))); + } finally { + if (isRemoteFile) { + Files.delete(target.toPath()); + } + } + } + } else if (request.getImageBytes() != null && request.getImageBytes().size() > 0) { + // The image was embedded in the request. Extract it to a file, then run BIQT on it. + File target = extractImageDataToFile(messageId, request); + if (target != null) { + try { + jsonObjects.addAll(submitContactRequest(providerName, Arrays.asList(target.getAbsolutePath()))); + } finally { + Files.delete(target.toPath()); + } + } + } else { + log.warn("[{}] The request did not contain an image path or image data.", messageId); + response.setResponse(errorMessageString); + response.setMessage("The request did not contain an image path or image data."); + response.setDetectionCode("-1"); + return true; + } + + return false; + } + + private synchronized List submitContactRequest(String providerName, List paths) { + return biqtInstance.runProvider(providerName, paths); + } +} diff --git a/biqt/src/main/resources/application.properties b/biqt/src/main/resources/application.properties new file mode 100644 index 0000000..57b4771 --- /dev/null +++ b/biqt/src/main/resources/application.properties @@ -0,0 +1,17 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +iwp.biqt.requestedProviders=BIQTContactDetector,BIQTIris +iwp.biqt.request_queue=iris.biqt.request +iwp.biqt.response_queue=iris.biqt.response +iwp.contactdetection.response_queue=iris.contactdetection.response +iwp.contactdetection.detection_threshold=0.95 +spring.activemq.broker-url=tcp://activemq:61616 +spring.main.web-application-type=none diff --git a/buffers/README.md b/buffers/README.md new file mode 100644 index 0000000..156f3bd --- /dev/null +++ b/buffers/README.md @@ -0,0 +1,41 @@ +> +> NOTICE +> +> This software (or technical data) was produced for the U. S. Government and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV (May 2014) - Alternative IV (Dec 2007). +> +> (c) 2024 The MITRE Corporation. All Rights Reserved. +> + +# Buffers Module + +The Buffers module contains the Protocol Buffer message definitions shared by the Iris Analysis Toolkit (IAT) components. Maven generates Java and Python classes from the `.proto` files during the build. + +This module is not a standalone service and does not require Docker or ActiveMQ Artemis to build. + +## Building the Module +### Prerequisites +- Java 17. +- Maven. + +### Build JAR +From the main `iat/` directory, run: +```sh +mvn -pl buffers -am package +``` + +The built JAR is written to `buffers/target/`. The generated Python modules are written to `buffers/target/python/buffers/`. + +### Install JAR +To install the Buffers JAR in the local Maven repository for use outside this source tree, run: + +```sh +mvn -pl buffers clean install +``` + +## Using the Module +The Web, BIQT, ACII, TSHEPII, PDM, and Inference components use the message schemas in `src/main/proto/`. When building a component with Maven's `-am` option, Maven builds Buffers automatically when it is a dependency. + +Java consumers use the generated classes in the `org.mitre.iwp.buffers` package. Python consumers can use the generated modules from `buffers/target/python/buffers/` or generate the modules directly with `protoc` for their own project. + +## Python Wheel +`src/main/python/setup.py` is an optional packaging helper for building a Python wheel from the generated protobuf modules. It is not required to build the Java JAR or generate the Python output. diff --git a/buffers/pom.xml b/buffers/pom.xml new file mode 100755 index 0000000..7711386 --- /dev/null +++ b/buffers/pom.xml @@ -0,0 +1,76 @@ + + + + 4.0.0 + + org.mitre + buffers + 1.0 + + IAT Protocol Buffers + Protocol Buffer definitions for the Iris Analysis Toolkit. + + + 3.25.6 + 3.11.4 + + + + + com.google.protobuf + protobuf-java + ${protobuf.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + com.github.os72 + protoc-jar-maven-plugin + ${protoc.jar.maven.plugin.version} + + + generate-sources + + run + + + com.google.protobuf:protoc:${protobuf.version} + + src/main/proto + + + + java + + + python + ${project.build.directory}/python/buffers + + + + + + + + + diff --git a/buffers/src/main/proto/Acii.proto b/buffers/src/main/proto/Acii.proto new file mode 100644 index 0000000..a5d6279 --- /dev/null +++ b/buffers/src/main/proto/Acii.proto @@ -0,0 +1,31 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +syntax = "proto3"; + +package org.mitre.iwp.buffers; + +message AciiServiceResponse { + string imageId = 1; + HorizontalAlignment horizontal = 2; + VerticalAlignment vertical = 3; +} + +enum HorizontalAlignment { + UnknownHorizontal = 0; + Left = 1; + Right = 2; +} + +enum VerticalAlignment { + UnknownVertical = 0; + Up = 1; + Down = 2; +} \ No newline at end of file diff --git a/buffers/src/main/proto/Biqt.proto b/buffers/src/main/proto/Biqt.proto new file mode 100644 index 0000000..0a47ab5 --- /dev/null +++ b/buffers/src/main/proto/Biqt.proto @@ -0,0 +1,25 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +syntax = "proto3"; + +package org.mitre.iwp.buffers; + +message BiqtResponse { + string imageId = 1; + int32 contrast = 2; + int32 irisDiameter = 3; + int32 percentVisibleIris = 4; + int32 quality = 5; + int32 imageHeight = 6; + int32 imageWidth = 7; + int32 irisCenterX = 8; + int32 irisCenterY = 9; +} \ No newline at end of file diff --git a/buffers/src/main/proto/ContactClassifier.proto b/buffers/src/main/proto/ContactClassifier.proto new file mode 100644 index 0000000..cba0a76 --- /dev/null +++ b/buffers/src/main/proto/ContactClassifier.proto @@ -0,0 +1,22 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +syntax = "proto3"; + +package org.mitre.iwp.buffers; + +message ContactClassifierResponse { + string imageId = 1; + string response = 2; + string message = 3; + string tmpPath = 4; + string detectionCode = 5; + string processTimeMS = 6; +} \ No newline at end of file diff --git a/buffers/src/main/proto/IrisAnnotation.proto b/buffers/src/main/proto/IrisAnnotation.proto new file mode 100644 index 0000000..4149d95 --- /dev/null +++ b/buffers/src/main/proto/IrisAnnotation.proto @@ -0,0 +1,67 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +syntax = "proto3"; + +package org.mitre.iwp.buffers; + +message IrisAnnotationQuery { + string id = 1; + ModeType mode = 2; + string imagePath = 3; + bytes imageBytes = 4; + string imagesFile = 5; + OptimizationOptions optimizationOptions = 6; + repeated ClassOption classOptions = 7; +} + +message OptimizationOptions { + int32 batchSize = 1; +} + +message ClassOption { + int32 index = 1; + string key = 2; + string value = 3; +} + +message IrisAnnotationReply { + string id = 1; + string response = 2; + string responseCode = 3; + string responseMessage = 4; + string responseTimeMS = 5; + repeated Annotation results = 6; +} + +message Annotation { + string id = 1; + string colorHex = 2; + repeated Polygon polygons = 3; +} + +message Polygon { + Points outline = 1; + repeated Points inline = 2; +} + +message Points { + repeated Point points = 1; +} + +message Point { + int32 x = 1; + int32 y = 2; +} + +enum ModeType { + single = 0; + multiple = 1; +} \ No newline at end of file diff --git a/buffers/src/main/proto/Iwp.proto b/buffers/src/main/proto/Iwp.proto new file mode 100644 index 0000000..bae0aff --- /dev/null +++ b/buffers/src/main/proto/Iwp.proto @@ -0,0 +1,32 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +syntax = "proto3"; + +package org.mitre.iwp.buffers; + +message ImageServiceQuery { + string imageId = 1; + string path = 2; + bytes imageBytes = 3; + enum ImageType { + PNG = 0; + JPG = 1; + JPEG = 2; + JP2 = 3; + BMP = 4; + TIF = 5; + TIFF = 6; + PIXMAP = 8; + } + ImageType imageType = 4; + int32 width = 5; + int32 height = 6; +} \ No newline at end of file diff --git a/buffers/src/main/proto/Messages.proto b/buffers/src/main/proto/Messages.proto new file mode 100755 index 0000000..be5a41d --- /dev/null +++ b/buffers/src/main/proto/Messages.proto @@ -0,0 +1,184 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +syntax = "proto3"; + +package org.mitre.iwp.buffers; + +message IrisImageBuffer { + string correlation_id = 1; + bool success = 2; + string message = 3; + + ImageBuffer image = 999; +} + +message CandidateListBuffer { + string correlation_id = 1; + bool success = 2; + string message = 3; + + repeated CandidateBuffer candidates = 4; +} + +message CandidateBuffer { + string subject_id = 1; + string master_name = 2; + string biometric_set_identifier = 3; + string image_type = 4; + string friction_ridge_generalized_position = 5; + string print_position_descriptor = 6; + double match_score = 7; + string biometric_image_available = 8; + string name_of_designated_repository = 9; + string information_designation_character = 10; + string note = 11; + string pose = 12; + string smt_code = 13; + string eye_label = 14; + + ImageBuffer image_buffer = 999; + string image_path = 1999; +} + +message ImageBuffer { + bytes data = 1; +} + + +message ImageServiceQuery { + string id = 1; + string path = 2; + bytes imageBytes = 3; + enum ImageType { + PNG = 0; + JP2 = 2; + BMP = 3; + + PIXMAP = 8; + + JPG = 10; + JPEG = 11; + TIF = 13; + TIFF = 14; + } + + ImageType imageType = 4; + int32 width = 5; + int32 height = 6; +} + +message TShepiiCompareRequest { + TShepiiResponse response1 = 1; + TShepiiResponse response2 = 2; +} + +message ServiceReply { + string id = 1; + AciiResponse aciiResponse = 2; + BiqtResponse biqtResponse = 3; + ServiceResponseType serviceResponse = 4; + string imageId = 5; + string imagePath = 6; + ContrastResponse contrastResponse = 7; + TShepiiResponse cryptResponse = 8; + TShepiiResponse cryptResponse2 = 9; + DmipaResponse dmipaResponse = 10; + ContactClassifierResponse contactclassifierResponse = 11; +} + +message AciiResponse { + HorizontalAlignment horizontal = 2; + VerticalAlignment vertical = 3; +} + +message BiqtResponse { + int32 contrast = 1; + int32 irisDiameter = 2; + int32 percentVisibleIris = 3; + int32 quality = 4; + int32 imageHeight = 5; + int32 imageWidth = 6; + int32 irisCenterX = 7; + int32 irisCenterY = 8; +} + +message ContrastResponse { + bytes imageBytes = 1; +} + +enum HorizontalAlignment { + UnknownHorizontal = 0; + Left = 1; + Right = 2; +} + +enum VerticalAlignment { + UnknownVertical = 0; + Up = 1; + Down = 2; +} + +message TShepiiResponse { + repeated TPoint keypoints = 1; + repeated Crypt crypts = 2; + repeated int32 matchIndex = 7; + TCircle cropIris = 3; + TCircle cropPupil = 4; + TCircle originalIris = 5; + TCircle originalPupil = 6; + int32 inputImageWidth = 8; + int32 inputImageHeight = 9; +} + +message TCircle { + TPoint center = 1; + double radius = 2; +} + +message TPoint { + double x = 1; + double y = 2; + double size = 3; +} + +message Crypt { + repeated TPoint points = 1; +} + +message DmipaResponse { + string filename = 1; + bool irisDetected = 2; + + string response = 3; + string message = 4; + string tmpPath = 5; + string detectionCode = 6; + string processTimeMS = 7; +} + +message ContactClassifierResponse { + string response = 1; + string message = 2; + string tmpPath = 3; + string detectionCode = 4; + string processTimeMS = 5; +} + +// May be unnecessary for now, but will assist asynchronous is wanted +enum ServiceResponseType { + UnknownServiceResponse = 0; + AciiServiceResponse = 1; + BiqtServiceResponse = 2; + DmipaServiceResponse = 3; + ContrastServiceResponse = 4; + TShepiiServiceResponse = 5; + ContactClassifierServiceResponse = 6; +} diff --git a/buffers/src/main/proto/Pdm.proto b/buffers/src/main/proto/Pdm.proto new file mode 100644 index 0000000..eb12954 --- /dev/null +++ b/buffers/src/main/proto/Pdm.proto @@ -0,0 +1,43 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +syntax = "proto3"; + +package org.mitre.iwp.buffers; + +// Request and Response for Dual PDM Image Comparison +message DualPDMComparison { + bytes probe = 1; + bytes candidate = 2; +} + +// Response Wrapper for Single Image PDM +message DeformerWrapper { + string imageId = 1; + map eyePreserveResponse = 2; +} + +message DeformedImage { + bytes imageData = 1; + float score = 2; + enum ImageType { + PNG = 0; + JPG = 1; + JPEG = 2; + JP2 = 3; + BMP = 4; + TIF = 5; + TIFF = 6; + PIXMAP = 7; + } + ImageType imageType = 3; + int32 width = 4; + int32 height = 5; +} \ No newline at end of file diff --git a/buffers/src/main/proto/Tshepii.proto b/buffers/src/main/proto/Tshepii.proto new file mode 100644 index 0000000..4148f61 --- /dev/null +++ b/buffers/src/main/proto/Tshepii.proto @@ -0,0 +1,51 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +syntax = "proto3"; + +package org.mitre.iwp.buffers; + +message TShepiiCompareRequest { + TShepiiResponse response1 = 1; + TShepiiResponse response2 = 2; +} + +message TShepiiCompareResponse { + TShepiiResponse cryptResponse = 1; + TShepiiResponse cryptResponse2 = 2; +} + +message TShepiiResponse { + string imageId = 1; + repeated TPoint keypoints = 2; + repeated Crypt crypts = 3; + repeated int32 matchIndex = 4; + TCircle cropIris = 5; + TCircle cropPupil = 6; + TCircle originalIris = 7; + TCircle originalPupil = 8; + int32 inputImageWidth = 9; + int32 inputImageHeight = 10; +} + +message TCircle { + TPoint center = 1; + double radius = 2; +} + +message TPoint { + double x = 1; + double y = 2; + double size = 3; +} + +message Crypt { + repeated TPoint points = 1; +} \ No newline at end of file diff --git a/buffers/src/main/python/setup.py b/buffers/src/main/python/setup.py new file mode 100755 index 0000000..caa7591 --- /dev/null +++ b/buffers/src/main/python/setup.py @@ -0,0 +1,34 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +import json + +import setuptools + +config = {} +README = "" + +with open("config.json", "r", encoding="utf-8") as fh: + config = json.load(fh) + +with open(config["root_path"] + "/README.md", encoding="utf-8") as fh: + README = fh.read() + +setuptools.setup( + name=config['name'], + version=config['version'], + description=config['description'], + long_description=README, + long_description_content_type="text/markdown", + url=config['url'], + packages=setuptools.find_packages(), + install_requires=['protobuf'], + python_requires=">=2.7" +) diff --git a/common/README.md b/common/README.md new file mode 100644 index 0000000..348a949 --- /dev/null +++ b/common/README.md @@ -0,0 +1,37 @@ +> +> NOTICE +> +> This software (or technical data) was produced for the U. S. Government and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV (May 2014) - Alternative IV (Dec 2007). +> +> (c) 2024 The MITRE Corporation. All Rights Reserved. +> + +# Common Module + +The Common module is an Iris Analysis Toolkit (IAT) Java library shared by the Web component. It provides image-processing utilities, EBTS date utilities, and shared IAT constants. +This module is not a standalone service and does not require Docker or ActiveMQ Artemis to build or test. + +## Building the Module +### Prerequisites +- Java 17. +- Maven. + +### Build JAR +From the main `iat/` directory, run: +```sh +mvn -pl common -am package +``` + +The built JAR is written to `common/target/`. + +## Using the Module +The Web component depends on this module for image handling and EBTS support. When building the Web component with Maven's `-am` option, Maven builds Common automatically. + +`ImageUtils` reads raw grayscale and RGB images as well as BMP, JPEG, JPEG2000, PNG, and TIFF images. `IwpEbtsUtils` formats and parses EBTS dates, and `IwpConstants` provides shared EBTS constants. + +## Run Java Unit Tests +The module includes unit tests for `ImageUtils` using sample image files in `src/test/resources/file-types/`. +From the main `iat/` directory, run: +```sh +mvn -pl common -am test +``` diff --git a/common/pom.xml b/common/pom.xml new file mode 100644 index 0000000..6fa0651 --- /dev/null +++ b/common/pom.xml @@ -0,0 +1,82 @@ + + + + 4.0.0 + + org.mitre + common + 1.0.0 + + 17 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-site-plugin + + true + + + + + + + org.junit.platform + junit-platform-console-standalone + test + 1.10.2 + + + com.github.jai-imageio + jai-imageio-jpeg2000 + 1.4.0 + + + org.apache.tika + tika-core + 2.9.1 + + + commons-io + commons-io + 2.15.1 + + + org.slf4j + slf4j-api + 2.0.12 + + + org.slf4j + slf4j-log4j12 + 2.0.12 + test + + + diff --git a/common/src/main/java/org/mitre/iwp/common/ImageUtils.java b/common/src/main/java/org/mitre/iwp/common/ImageUtils.java new file mode 100644 index 0000000..4e2a354 --- /dev/null +++ b/common/src/main/java/org/mitre/iwp/common/ImageUtils.java @@ -0,0 +1,84 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.common; + + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.awt.image.Raster; +import java.io.*; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ImageUtils { + + private static final Logger log = LoggerFactory.getLogger(ImageUtils.class); + + public static BufferedImage read(String cga, int hll, int vll, int bpx, InputStream inputStream) { + + try { + return read(cga, hll, vll, bpx, IOUtils.toByteArray(inputStream)); + } catch (IOException e) { + log.error("Could not read input stream", e); + } + + return null; + } + + public static BufferedImage read(String cga, int hll, int vll, int bpx, byte[] data) { + int bytesPerPixel = bpx / 8; + + if(cga.equals("NONE") && (hll * vll == data.length/bytesPerPixel) && bpx == 8){ + log.trace("This is a greyscale image"); + + BufferedImage image = new BufferedImage(hll, vll, BufferedImage.TYPE_BYTE_GRAY); + image.setData(Raster.createRaster(image.getSampleModel(), new DataBufferByte(data, data.length), new Point() ) ); + + return image; + } else if(cga.equals("NONE") && (hll * vll == data.length/bytesPerPixel) && bpx == 24){ + log.trace("This is an RGB image"); + + BufferedImage image = new BufferedImage(hll, vll, BufferedImage.TYPE_3BYTE_BGR); + image.setData(Raster.createRaster(image.getSampleModel(), new DataBufferByte(data, data.length), new Point() ) ); + return image; + } else if(cga.equals("NONE") && (hll * vll != ( data.length / bytesPerPixel ))){ + log.info("datalength: {} hll: {} vll: {} bpx: {} cga: {}", data.length, hll, vll, bpx, bytesPerPixel); + return null; + } + + ByteArrayInputStream bais = new ByteArrayInputStream(data); + return convertImage(cga, bais); + } + + + /* + ImageIO recognizes the following formats: BMP, GIF, JPEG, PNG, TIFF, WBMP + jai-imageio-jpeg2000 plug-in was included to deal with JPEG2000 + */ + private static BufferedImage convertImage(String cga, InputStream is){ + + try { + BufferedImage image = ImageIO.read(is); + return image; + } catch (IOException e) { + log.error("This is not a recognized image type!"); + } catch (Exception e) { + log.error("Uncaught error: {}", e.getMessage()); + } + + return null; + } +} + diff --git a/common/src/main/java/org/mitre/iwp/common/IwpConstants.java b/common/src/main/java/org/mitre/iwp/common/IwpConstants.java new file mode 100644 index 0000000..510fc72 --- /dev/null +++ b/common/src/main/java/org/mitre/iwp/common/IwpConstants.java @@ -0,0 +1,27 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.common; + +public class IwpConstants { + + public static final String TOT_EBTS = "EBTS"; + public static final String TOT_IIDS = "IIDS"; + + // Type 1 Default Field Values + public static final String VER = "0500"; // 1.2 + public static final String NSR_00_00 = "00.00"; // 1.011 + public static final String NTR_00_00 = "00.00"; // 1.012 + public static final String DOM_DNM = "NORAM"; // 1.013a + public static final String DOM_DVN = "EBTS 10.0"; // 1.013b + public static final String APS_APO = "APO"; // 1.016a + public static final String APS_APN = "APN"; // 1.016b + public static final String APS_APV = "1.0"; // 1.016c +} diff --git a/common/src/main/java/org/mitre/iwp/common/IwpEbtsUtils.java b/common/src/main/java/org/mitre/iwp/common/IwpEbtsUtils.java new file mode 100644 index 0000000..2fada29 --- /dev/null +++ b/common/src/main/java/org/mitre/iwp/common/IwpEbtsUtils.java @@ -0,0 +1,35 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ +package org.mitre.iwp.common; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Optional; + +public class IwpEbtsUtils { + private static final String EBTS_DATE_FORMAT_STRING_JAVA = "yyyyMMdd"; + + public static String convertToEbtsDate(Date date) { + assert date != null : "date must not be null"; + SimpleDateFormat sdf = new SimpleDateFormat(EBTS_DATE_FORMAT_STRING_JAVA); + return sdf.format(date); + } + + public static Optional parseEbtsDate(String date) { + assert date != null : "date must not be null"; + SimpleDateFormat sdf = new SimpleDateFormat(EBTS_DATE_FORMAT_STRING_JAVA); + try { + return Optional.of(sdf.parse(date)); + } catch(ParseException parseException) { + return Optional.empty(); + } + } +} diff --git a/common/src/test/java/org/mitre/iwp/common/ImageUtilsTest.java b/common/src/test/java/org/mitre/iwp/common/ImageUtilsTest.java new file mode 100644 index 0000000..1b00a75 --- /dev/null +++ b/common/src/test/java/org/mitre/iwp/common/ImageUtilsTest.java @@ -0,0 +1,236 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.common; + + + +import org.apache.commons.io.IOUtils; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.imageio.ImageIO; +import java.awt.*; + +import java.awt.image.BufferedImage; + +import java.io.InputStream; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class ImageUtilsTest { + + private static final Logger log = LoggerFactory.getLogger(ImageUtils.class); + + + ClassLoader classloader = Thread.currentThread().getContextClassLoader(); + + + @Test + public void testPixMapGrey() throws Exception { + + log.trace("---------- testPixMapGrey ----------"); + + InputStream input = classloader.getResourceAsStream("file-types/image-bitmap.byte.gray"); + + BufferedImage result = ImageUtils.read("NONE", 100, 67, 8, input); + + assertNotNull(result, "Greyscale null test"); + assertEquals(100, result.getWidth(), "Greyscale width test"); + assertEquals(67, result.getHeight(), "Greyscale height test"); + + } + + @Test + public void testPixMapRGB() throws Exception { + + log.trace("---------- testPixMapGrey3 ----------"); + + InputStream input = classloader.getResourceAsStream("file-types/image-bitmap.rgb"); + + BufferedImage result = ImageUtils.read("NONE", 100, 67, 24, input); + + assertNotNull(result, "Greyscale null test"); + assertEquals(100, result.getWidth(), "Greyscale width test"); + assertEquals(67, result.getHeight(), "Greyscale height test"); + + } + + + @Test + public void testBitmap() throws Exception{ + + log.trace("---------- testBitmap ----------"); + + InputStream input = classloader.getResourceAsStream("file-types/image-bitmap.bmp"); + + Image result = ImageUtils.read("BMP", 100, 67, 24, input); + + BufferedImage origImage = ImageIO.read(classloader.getResourceAsStream("file-types/image-bitmap.bmp")); + BufferedImage resultImage = toBufferedImage(result); + + assertTrue(compareImages(origImage, resultImage), "Success: BMP Test 1"); + + } + + @Test + public void testBitmap2() throws Exception{ + + log.trace("---------- testBitmap2 ----------"); + + InputStream input = classloader.getResourceAsStream("file-types/image-bitmap.bmp"); + + // convert file to byte array + byte[] inputBytes = IOUtils.toByteArray(input); + Image result = ImageUtils.read("BMP", 100, 67, 24, inputBytes); + + BufferedImage origImage = ImageIO.read(classloader.getResourceAsStream("file-types/image-bitmap.bmp")); + BufferedImage resultImage = toBufferedImage(result); + + assertTrue(compareImages(origImage, resultImage), "Success: BMP Test 2"); + + } + + @Test + public void testJPEG2000() throws Exception{ + log.trace("----------test jpeg 2000----------"); + InputStream input = classloader.getResourceAsStream("file-types/image-jp2.jp2"); + + byte[] inputBytes = IOUtils.toByteArray(input); + BufferedImage ret = ImageUtils.read("JP2", -1, -1, -1, inputBytes); + + assertEquals(100, ret.getWidth(), "Check JP2 image width"); + assertEquals(67, ret.getHeight(), "Check JP2 iamge height"); + } + + @Test + public void testJPEG() throws Exception { + + log.trace("---------- testJPEG ----------"); + + InputStream input = classloader.getResourceAsStream("file-types/image-jpeg.jpeg"); + + BufferedImage result = ImageUtils.read("JPEG", 100, 67, 24, input); + + assertEquals(100, result.getWidth(), "Check JPEG image width"); + assertEquals(67, result.getHeight(), "Check JPEG iamge height"); + + } + + @Test + public void testJPG() throws Exception { + + log.trace("---------- testJPG ----------"); + + InputStream input = classloader.getResourceAsStream("file-types/image-jpeg.jpg"); + + BufferedImage result = ImageUtils.read("JPG", 100, 67, 24, input); + + assertEquals(100, result.getWidth(), "Check JPG image width"); + assertEquals(67, result.getHeight(), "Check JPG iamge height"); + + } + + + @Test + public void testPNG() throws Exception { + + log.trace("---------- testPNG ----------"); + + InputStream input = classloader.getResourceAsStream("file-types/image-png.png"); + + Image result = ImageUtils.read("PNG", 100, 67, 24, input); + + if(result == null){ + System.out.println("result is null"); + } + + BufferedImage origImage = ImageIO.read(classloader.getResourceAsStream("file-types/image-png.png")); + BufferedImage resultImage = toBufferedImage(result); + + assertTrue(compareImages(origImage, resultImage), "Success: PNG Test 1"); + } + + @Test + public void testPNG2() throws Exception{ + + log.trace("---------- testPNG2 ----------"); + + + InputStream input = classloader.getResourceAsStream("file-types/image-png.png"); + + // convert file to byte array + byte[] inputBytes = IOUtils.toByteArray(input); + Image result = ImageUtils.read("PNG", 100, 67, 24, inputBytes); + + BufferedImage origImage = ImageIO.read(classloader.getResourceAsStream("file-types/image-png.png")); + BufferedImage resultImage = toBufferedImage(result); + + assertTrue(compareImages(origImage, resultImage), "Success: PNG Test 2"); + + } + + @Test + public void testTIF() throws Exception{ + + log.trace("---------- testTIF ----------"); + + + InputStream input = classloader.getResourceAsStream("file-types/image-tif.tif"); + + BufferedImage result = ImageUtils.read("TIF", 100, 67, 24, input); + + assertEquals(100, result.getWidth(), "Check TIF image width"); + assertEquals(67, result.getHeight(), "Check TIF iamge height"); + } + + public static BufferedImage toBufferedImage(Image img) + { + if (img instanceof BufferedImage) { + return (BufferedImage) img; + } + + // Create a buffered image with transparency + BufferedImage bimage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB); + + // Draw the image on to the buffered image + Graphics2D bGr = bimage.createGraphics(); + bGr.drawImage(img, 0, 0, null); + bGr.dispose(); + + // Return the buffered image + return bimage; + } + + public static boolean compareImages(BufferedImage imgA, BufferedImage imgB) { + // The images must be the same size. + if (imgA.getWidth() == imgB.getWidth() && imgA.getHeight() == imgB.getHeight()) { + int width = imgA.getWidth(); + int height = imgA.getHeight(); + + // Loop over every pixel. + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + // Compare the pixels for equality. + if (imgA.getRGB(x, y) != imgB.getRGB(x, y)) { + return false; + } + } + } + } else { + return false; + } + + return true; + } +} diff --git a/common/src/test/resources/file-types/image-bitmap.bmp b/common/src/test/resources/file-types/image-bitmap.bmp new file mode 100644 index 0000000..acf5f4a Binary files /dev/null and b/common/src/test/resources/file-types/image-bitmap.bmp differ diff --git a/common/src/test/resources/file-types/image-bitmap.byte.gray b/common/src/test/resources/file-types/image-bitmap.byte.gray new file mode 100644 index 0000000..025374e --- /dev/null +++ b/common/src/test/resources/file-types/image-bitmap.byte.gray @@ -0,0 +1,7 @@ +¸p|yotoĨz˴ne|mysmmyv~xvtdzݳ~z|wscb5Vu}|yu~t͹rp~U~qxv|xut{w|Ǽ}}rz­q|~}y}zvlz~qu}{y~zt~zs]Bv}y}y|xylrga~{uTBYbw~Ʒs{v|}wcdk}vX$8ds_lkon}}zlsz|{z~ktf]&HifZ/cpu{~yrt{wow||po}{m_jvxuwyw}q|z||o}~{B2:R|Ѳwz|z{{xrz}q{olumw~su{CiEJW`{}}xkoppegonsu{vtVC/1.FMj|qnj^pxre^isya`nuyctwliA"[Fjmt{}}wkahlf`R^bYERCQ\biX_js~mFH@1D\ƭ~cZZRXD@MF247IEKONXm_n~h&.Ql|hfkj_UTJL?2*-1:DGJQLOgpgzD#9`{isrgcpmYMJK=;1(#-5:6=7-%".63141_FVM+42%-=Tpput{wygb\]emloq[Ypqh_H82F;:7.4"!$#)*}V]K;" $+;L_iv~tuԻ«dVaUUdXjomajRAe\yZFA54I>9?F?%,796P`bbaO@ ,DG[Znwvu{|hFUp3dq\J9[JYD3:AIIWuV^`INShznv\X@!/+GP]bZO^YFSNCGO_]QnjB1!ASY\S[X- <3HTB_pGW]$Z=.+$"%;m+{NWcVWonnwjB$0LCCLP6<'2,+ +  '*7X_[D<;0C̵ƹpijuxopu{o\GG2'<= pN2$*X2CKP~q_fcSk`J&BRVQ?H?7E_B?<@3% &0891.-4ſyvor}tnzlm]V\5.#H_ 351IH?z{kdfQ^@\]TNTRK91X):GMLA;1# !'ggŒjnt~trrukl|{oqurhR93!,$)P;G=%5J~NdS5hO%+3ObZ[?4HH8@4\JRJLBA:-  + + _k|edosspomdlTsfIO50&Q}C9")Wi/)ykWIEyh,Cb[`MBOPIH=:452_wnyrgTxHBX[RmyîwY<3[\XyqnpojF+08NfSY1,^lUCDMUSc`gqxpcUExknv}ng^H!Mķ|yjmf?>Rf^-8.5EG)KZSRW\7"Ka[YH\ruiyȰônhiE'%*riƤzwssF-C)?2$Jc`[Y]U'$QZ_WZ\W`KVeͿnƳĠtpj\C.&,Ÿwp|y\:AT2"XXXRe$&P\bbW-6Qa\Rȼñzu®ѵȧnLmnP.-(,7̾wzxxt@ .]^`GXw+RijZ/&4Y^\+»ž̶ĿMatgM33F9;cs~[-9sn]\XH,4Wa`?iJ/]fS#ȹ­Ψwpwuvjg\>-)&Sm}o;ipR+>g]a%WI8fj^{ÿìr˻»zlckonbc;/2/3Iдy?z}Wgcrh)&.FvYQFŽjaut{XEO[x{L6wдqm|tmN\ytssb|úuitku||{~lieJ%361bĶqy~˻kUST~mfc^ivqon34B6:2RJt¦²ɿ{ztt:Z_iq~blP|qj_pwVHqg`cT<7-=;aĥ}^fiRygPmq{~TY~id^[jzoroqL90+-,6|57*/5Ypufjqzzp|Wɼ[mzbH@stzt{|wR;7G_yf?84(0ķĤxfkiaHqĪȽ{jwcZszzyilvutstkjb̴¹wro\gr_jomNV{}qv~tĺȾɿ}m~rut{bhv]Hu÷|xyzwdcqvsD>B[ú}xnihcecvmob[Y`c|}}}ytseQeºffpdsrke\^קtj`ZnorgFtpujl`v¼ǹf_u`_iuqxszצ~q_\cd[qoxly^vCcwvxdtúqVrz}nPpڲ|ɐ{s\htz[xvû{^nqjeb7`³l\cyfut[QX08||eʻvVhp|rsqO,Pwcɍf~~}|cV{|}^ԼtRw{tpʼŶvmlrxz|u\orxhtHʝk_ctoqmPSe~~{xmlw{z~mgs~cffxkxuǽk \ No newline at end of file diff --git a/common/src/test/resources/file-types/image-bitmap.rgb b/common/src/test/resources/file-types/image-bitmap.rgb new file mode 100644 index 0000000..8ab4ba6 --- /dev/null +++ b/common/src/test/resources/file-types/image-bitmap.rgb @@ -0,0 +1,9 @@ +ɨ~~j`^pb_lccvx{~nji}vv}|wusǺČpihqmnqqq³nllUOLZRNheb~|~||~vtturikrij}tuxzuy{}qkljefxpr}~{rprpoҷDzvwuonjplispruuwwz{{qscYXtmlzpmutndfyqqJHF֏ihqg[[j_`|ppw~~}{z~zwha_YUS~x{xwyz^\_宵mnXOPsrwv~~ñƔopp{||xqn~omzmmWKJ1,(SRJ~ttg`bևkbez}fffstwusuaXWZMKdTQ5.,;:4~}x~z|~jbdֳȌwryWTWpdeźzw^^_mmorqpqgfp``l^]:64bb[qijz~_Z\h`btwź¹ƽsqnVVWrqs|zywxwijUMNton䳭zxˑ}}~nti|nn|}¡`X]z|ldg||onuwaXWru|rtĵƸƸffg^_azrri][h[[oeg}qrvmpwu}ypq~wyᣣz~vmod[]cYYl_]q`ZfXRvnpslo|~zuynhmzv|][cyrvH@Ai_`rqnirtg`d|poz{ĻżŻeegeeh}vmmjc`|oo{mntfgm`cWYd_\^ߥ~norefi[[aVWtjlsln{vqpiefGDGqpzpluxqvngjphkc\`z~|rtļ}{~xwztkknffnfeia`ma`h``uvxoknwjkj^^cXUbWS}WKyZUz{|zz|yxt~vpupqwbcjOOUhbd¾ûy|qlk~ywqmyrswsswxj_^rdaytu_Y^HEJtrx˰äzy~tv|vy|v{xtxcahg`ctnq{su|pmGAE?;AD?D`WZ|̼˸ǿ|svvnruklaYYpccuroƫd[]|c`ώtkmmipLJSXRWxy{x~}zml~rrzy{~yosſótvkbdohkoefj^[KGFznjĩѵƜ~x|}mgk||tvgab^WZxprvw~vyvvsedwhfzrv{pt{os{onpmwgdsfej^]¿þƝxll}qpmbbqffznl|xyxxrmqy{UUZҹ\[Ymjp|w{c_cJEJ@=Czoqxqtvz}ppwxxxspZOM<8=74:LDEl[Vtrqb^F<;<56715(&+1.2ȿŽĄ{wf\YnbcqefZONvote]YNIqkktpnw{y{\PPXX[~}jgk~}}na^gZXcXVrgevyvklxnpwljznlRIH>680-2)(/+*2)*0837yo91455<)(./,2527\WX¸º|rwr}oo~op?87bXSg^Xjjg´Ÿtmrz~f_asgiRNP{ypntlglb\`umo}st]TROEE}nh{ysd_i\Zl_]_QK>:=54;,*1A:<<8=c_b}~ƫͼþwhc]ROZRNWUO|{|ljpgchc_fcZ\pferrwmptlnxjg207417L?<;3497=1/5QOUNGJurt¾þŵyzƾqnsebxsp{zy~iinwruxyywwwy|shjpefna`SGD$#(01953;0.5cabƶ|~}lhts||xwľ¹źhgng^^zqsyoswxaVRMB?VMJbXVtsxwSIH43:xvxз|z{mjvigxy~pnj\\KCjXRuqolmlnlp^W}z}yýµ{~wpo˺õ²ɫzv~liof`dqhhvlksklh_`n_[A97.,20/6.,3404OFEϸur>poeUP_NFUC;ɺľ÷ϽҢ{~{qytlo}tu}qq~sqtgdqcagYTC;<+*2('-$#*TQSzjdyu{x}kd@7:xe^}lhbOD.(+E;9td^hYVp\V&#'[UVNGFA51sa^vedkZVĹƾϵļ¿QJMi^]}pprd_ZJD701603NC@?76<9cSRɀuq,(,bOEudcsdfcWT6-+&%(823iULjYXhXT2)'ëƽӼŚpkpȳ°Ŝ{ppzniugajYRL@<0+.&$),*-|½|zqm}kgwuunlYOC74H>>eQE9//# "}|cSRdTOcTQ]NLoa^#"'&$)aMCgWUm\\l^[fRM9+$>30`NDl\ZhWTcPDwtk˯ƳpkooedredLB@+%%(#$,()yqllggŪwl{urnnkojQC?1*+D@D*'*H<98//%#%OGGk`_l[XgWTdUSiYVcQK*$&&"%[NKeVTjZYdROhVPjYQeULl\WWGD`RQoa_usžùjlknnnyuu{{vkhpdbh[XSEA!"RMGĽxvupwfa{icwcYG<9J<4dNCub\lZT.+.<660+.:23QBO>74.-ha`$!$C96~soM?:m_\bTQC75/&%A4.dRJl\Yrccugh|noqden`__VS3-+YMIZLFcUP<3/'"0(!bUL^TRRGBPC8NC8ND@JA:D=63,$`\T|yzztyupuqmsrnxlhr`\engijabUMNvkjyyZPNeTNj\X`QITEm[TiVLgVNfXSfXUn]WspsgefXVTF?& 0%>4,j[Ul^[TIGTIDWNGZOIK@6TI@XOJ\SQHA>94xvrXTPLH@FC:IE;20%jhbgie|ƪŎnejmiurn|{ytpyol{ol|qnlgojhsxvxumixrmt|prynopdc\OJC61>0,* 5+#0"8']NDF91XF8M<-.$<40UH@zvYKEn`]bOG>2/{{sd`[NE+# 5*":3,[NCk_\bWUdXTH>6>4*QG@NGB>91J@7<4,c]UPIDZTGTL?SMDHD9GC:?<41/'%#   ¾͸Ǧ|t{xqzqjqslv}z|zwsn|oht}v{nhmqjlbZ\_RPgXU>2.6,(-!$QH?l_R,C1%D4(>0&XG;XF:J>6¿wqwtwhctaXrc]]NHiZV~}M?5gZSj\Q`SHZNB\RNZPMRIFB81:1'^XS1*!A<0NGASMFSNBHD7C?/64(&& + +  + $#++թßˮérlrneioejvpzws~oiuki{qn}vwjkcXYJCGNCD5.2(%'D96H95&ymjYKE?0(' 0"8(eVM<1(QB6XJ>^NC{w}pfn\Tra^o_\]PMugei[[THB-%NA6[QJ_TP\PHH>6OGBE?:=71LD@e_ZIC;G@6C>4GC786**(  +  *)91%A:-?>-55(13%10%:9(ԧyqfhqgksmwrksv}f`gg\_rgi[MOxoqdWWF76<-)/$"900I:6gUQF:66,)4($.!+ /#J9/}j_5*#yr[MBcULn`ZdUJcUL|jhzhgwgk~pvpdiJ>=*$6/*QKHJC=HB>RLGUPK;71C<6,) 84*2-%.-%  #!$$+,./!=<,`\Mb`Zb_PJH;A@3?<542+JD;ųxpu|sz}szwowzkhraXXHAUHC;/+A1,:+#>-$8.+vnn}ro<51vsJ=9A40L>:>3/3($VGDYH@`K:C7-K?8}XH?820(&"1-*83.>94:5/81+&#0.'('!  (%32$F?+?<*;;+AA.IJ9KK8a]MSNGPLBE?:1,(C:7IA:dUOŴvjkz|f[L?::/*' J@9aOIfUOmWObOIgWTeUO3,( D;5?1*TDA_OMK>=h[ZxmjSD>hUIl[O+#K;/P@5WICXLGSE;oYMygb~mki\\D98@76LA@80/#" ,($/-&"  ! *)..%96*KF<\WHPM:DD7TTIcbZnlaaXOqe`92/QIEF?=RJG>96FAAݽ|qvwtk]J<2]M@&,%!nb\hXTbQMP?8\MJdROeTR[NJ 4.-fWRtcbraaYLL]OK/%]OJve`|sSKHOE>YJ?cUNg[Xsb[zzu{jejZZnbbC87QFE6,+-(%  ! ,, 10%CC5NL:\W44.)' +  ""30&@?1PPHYYK`[Nc_SifXqmarofkkgvwn{|tvwkoofnlcwunzstpqsmz}|®ylzeZJ:/hSGXF<^OF[KGVHC`PLE96D<;IDEYSTzli?1)oa[{mjfXUUHAB82_ZYOHGeVPPB::0.B85G@;QIBPL?`[K|xjZXOb`YeaYNKBROHXSMngbyr|hfpreWYbSTI<<'  32'1-#A@4RRKpnnee\ihanmhnoiw{z~~tuqpsu||{}պĩucTbRNo\W^RP]PQj`a`TUtfd}ld~wzkbw|k_XsfdZOMK@9lbabX[vweWRSDJIC^^U\\Uppiyyr|wwtwwp{{xz~|}~tkuptc]n_XzhZSd\W}pd[vmdvkdxnhxplaZU_WUunmylmocej[YQEC@7174+JFA;=99::678-.0337"#!##''&'!"--"-,${ueRLhXVUGFC86' "  %%",,)<<8ONFa`\lmcxxs{}suumqz}uykzrbrj]ndW_WFi_MSK?ypa|zuec_~skmd\]qijYPMHC>;UUSprnrsluvtuus{}x|vonevvoy{rwyopphstkghb~ea]]ZUfebrnjMLFOQMLMI687)+*.//,-.&'%!"!##!--*664=<6==5A?7KJ;LK=;;:121'((#$#-.,654==:DD@NODII>JJ=FF>^^PXWNhhdn^`1($$%$&&%OOQopq|}~}|}{ijfgickmigin]^bTTWQSXLKHNMH?@?231,,'/0)44,;<8EEBIICLLFTUIQRBRSHjjbsrllj`z}usO??(#78;^`cy}}dea\^T]_RSTNXYWEEBAA>OPIHGB330760<71KKFII>MNERSIQQH[\QpqebcXqqgtwtca~y-%--0NPTfivz|~yxupihbb_kkappdjj^ac\UUKaaWfd\`]NJJTF?L=7}"22/DFA\^Zx|ˬҫǛz|}prpqpjpkdb][sskwxyusohg_aaYsl]ytk|{uec\cbZtqf|yj|{shhYxykyzqoogrfdG@<(""cZULE@mmcnnkuwpz}zz~}z}{~}{}~}{xvnnesshrskrrlfgaml]zu^wqbxvlz|vnwqxyqztn^UPGC?20*61+2/)HGBRMImiiwy{}}~yzz{wx{}xz}puvkz|yxtsk{vstrhrob~yhuqb~{lxwxvl{{{tuwzz}NA;qfcKC@LJHZWT```yz}”¿|tultur{{z|y~y}nzrvfw{tz}}|~vnpqprlv}uqmhe]Zllfyzp|{rze}e|m}zo~vrqo~~w|}t~|y}||qnn~w}}|zzHA=81.?87UQPyzӲwyu~~|{}}s}q}ulngz}tvslg_fYW+%"KHEnic{keb[ZX1./hd^{~ľ®rrmwxq}~v~}x{y}}tpbUP)$ >83ff_~~~~xvk__`llklljrrjprix}}zzvlojsvo{~vx|õ|t||sq[SOHB;^[RicYwyt}~~Ÿ~tuo|z{yzn}x}zw|rcf_eh`lnfv~}xqpfYWJA /etc/iwp-acii/acii.conf + +exec aciiComponent /etc/iwp-acii/acii.conf /etc/iwp-acii/logger.conf false /var/log/acii.log \ No newline at end of file diff --git a/iris-workstation/iwp-simple/iwp-biqt/config/application.properties b/docker/biqt-component/config/application.properties similarity index 69% rename from iris-workstation/iwp-simple/iwp-biqt/config/application.properties rename to docker/biqt-component/config/application.properties index 745dd62..645c1f6 100644 --- a/iris-workstation/iwp-simple/iwp-biqt/config/application.properties +++ b/docker/biqt-component/config/application.properties @@ -8,14 +8,10 @@ # (c) 2024 The MITRE Corporation. All Rights Reserved. # -#possible iwp.biqt.requestedProviders=BIQTContactDetector BIQTFace BIQTIris iwp.biqt.requestedProviders=BIQTIris,BIQTContactDetector iwp.biqt.request_queue=iris.biqt.request iwp.biqt.response_queue=iris.biqt.response iwp.contactdetection.response_queue=iris.contactdetection.response iwp.contactdetection.detection_threshold=0.95 -# spring.activemq.broker-url=tcp://localhost:61616 -# tcp://activemq:61616 is for running with activemq on docker-composer -# tcp://localhost:61616 is for running with activemq on docker local spring.activemq.broker-url=tcp://activemq:61616 spring.main.web-application-type=none diff --git a/iris-workstation/iwp-simple/composeScript.sh b/docker/composeScript.sh similarity index 100% rename from iris-workstation/iwp-simple/composeScript.sh rename to docker/composeScript.sh diff --git a/docker/docker-compose.local.yml b/docker/docker-compose.local.yml new file mode 100644 index 0000000..b993805 --- /dev/null +++ b/docker/docker-compose.local.yml @@ -0,0 +1,175 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +# Important +# To deploy this Compose file, set the following environment variables in a `.env` file: +#DATABASE_PASSWORD, DATABASE_USERNAME, DATABASE_ROOT_PASSWORD, DATABASE_NAME +# See the following Docker documentation for details about `.env` files: +# https://docs.docker.com/compose/environment-variables/set-environment-variables/#compose-file + +services: + activemq: + image: dhi.io/activemq-artemis:2 + container_name: activemq + restart: always + ports: + - 127.0.0.1:8161:8161 + - 127.0.0.1:61616:61616 + - 127.0.0.1:61613:61613 + environment: + ARTEMIS_USER: ${ARTEMIS_USER} + ARTEMIS_PASSWORD: ${ARTEMIS_PASSWORD} + ANONYMOUS_LOGIN: false + EXTRA_ARGS: --http-host 0.0.0.0 + + # web: + # image: ghcr.io/mitre/iat/iwp-web:latest + # restart: always + # depends_on: + # mysql: + # condition: service_healthy + # activemq: + # condition: service_started + # container_name: web + # ports: + # - 8080:8080 + # volumes: + # - "./web-component/config:/app/config" + # environment: + # - DATABASE_HOST=iwp_mysql + # - DATABASE_USER=${DATABASE_USERNAME} + # - DATABASE_PASSWORD=${DATABASE_PASSWORD} + # - DATABASE_NAME=${DATABASE_NAME} + # - DATABASE_PORT=3306 + # - JASYPT_ENCRYPTOR_PASSWORD=${JASYPT_ENCRYPTOR_PASSWORD} + # env_file: .env + # platform: linux/amd64 + + iwp-acii: + image: ghcr.io/mitre/iat/iwp-acii:latest + container_name: acii + restart: always + depends_on: + - "activemq" + volumes: + - "./acii-component/config/acii.conf.template:/etc/iwp-acii/acii.conf.template:ro" + - "./acii-component/config/start-acii.sh:/etc/iwp-acii/start-acii.sh:ro" + environment: + ARTEMIS_USER: ${ARTEMIS_USER} + ARTEMIS_PASSWORD: ${ARTEMIS_PASSWORD} + command: ["sh", "/etc/iwp-acii/start-acii.sh"] + + iwp-biqt: + image: ghcr.io/mitre/iat/iwp-biqt:latest + container_name: biqt + restart: always + depends_on: + - "activemq" + volumes: + - "./biqt-component/config:/app/config" + environment: + spring.activemq.user: ${ARTEMIS_USER} + spring.activemq.password: ${ARTEMIS_PASSWORD} + platform: linux/amd64 + + # Iris Annotation via CPU + iwp-annotation: + image: ghcr.io/mitre/iat/iwp-annotation:latest + container_name: annotation + restart: always + depends_on: + - "activemq" + environment: + - STOMP_HOST=activemq:61613 + - STOMP_USER=${ARTEMIS_USER} + - STOMP_PASSWORD=${ARTEMIS_PASSWORD} + - STOMP_HEARTBEATS=10000,0 + platform: linux/amd64 + + # Iris Annotation via GPU + # iwp-annotation: + # image: ghcr.io/mitre/iat/iwp-annotation:latest + # container_name: annotation + # restart: always + # depends_on: + # - "activemq" + # environment: + # - STOMP_HOST=activemq:61613 + # - STOMP_USER=${ARTEMIS_USER:-artemis} + # - STOMP_PASSWORD=${ARTEMIS_PASSWORD} + # - STOMP_HEARTBEATS=10000,0 + # platform: linux/amd64 + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: all + # capabilities: [gpu] + + iwp-tshepii: + image: ghcr.io/mitre/iat/iwp-tshepii:latest + container_name: tshepii + restart: always + depends_on: + - "activemq" + volumes: + - "./tshepii-component/config/tshepii.conf.template:/etc/iwp-tshepii/tshepii.conf.template:ro" + - "./tshepii-component/config/start-tshepii.sh:/etc/iwp-tshepii/start-tshepii.sh:ro" + - "./tshepii-component/config/log4cxx.conf:/etc/iwp-tshepii/log4cxx.conf:ro" + environment: + ARTEMIS_USER: ${ARTEMIS_USER} + ARTEMIS_PASSWORD: ${ARTEMIS_PASSWORD} + command: ["sh", "/etc/iwp-tshepii/start-tshepii.sh"] + platform: linux/amd64 + + iwp-pdm: + image: ghcr.io/mitre/iat/iwp-pdm:latest + container_name: pdm + restart: always + depends_on: + - "activemq" + environment: + STOMP_HOST: activemq:61613 + ARTEMIS_USER: ${ARTEMIS_USER} + ARTEMIS_PASSWORD: ${ARTEMIS_PASSWORD} + STOMP_HEARTBEATS: 10000,0 + platform: linux/amd64 + + mysql: + image: mysql:8 + container_name: iwp_mysql + restart: always + environment: + MYSQL_DATABASE: ${DATABASE_NAME} + # Allows database access without using the root account. + MYSQL_USER: ${DATABASE_USERNAME} + # Password for the non-root database account. + MYSQL_PASSWORD: ${DATABASE_PASSWORD} + # Password for root access + MYSQL_ROOT_PASSWORD: ${DATABASE_ROOT_PASSWORD} + healthcheck: + test: mysqladmin ping -h localhost -u $$DATABASE_USERNAME --password=$$DATABASE_PASSWORD + interval: 1s + timeout: 1s + retries: 50 + ports: + # : + - '127.0.0.1:3306:3306' + expose: + # Exposes port 3306 within the Docker network. + - '3306' + # Persists database data. + volumes: + - my-db:/var/lib/mysql + env_file: .env + +volumes: + my-db: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..b24b399 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,168 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +# Important +# To deploy this Compose file, set the following environment variables in a `.env` file: +#DATABASE_PASSWORD, DATABASE_USERNAME, DATABASE_ROOT_PASSWORD, DATABASE_NAME +# See the following Docker documentation for details about `.env` files: +# https://docs.docker.com/compose/environment-variables/set-environment-variables/#compose-file + +services: + activemq: + image: dhi.io/activemq-artemis:2 + container_name: activemq + restart: always + environment: + ARTEMIS_USER: ${ARTEMIS_USER} + ARTEMIS_PASSWORD: ${ARTEMIS_PASSWORD} + ANONYMOUS_LOGIN: false + EXTRA_ARGS: --http-host 0.0.0.0 + + web: + image: ghcr.io/mitre/iat/iwp-web:latest + restart: always + depends_on: + mysql: + condition: service_healthy + activemq: + condition: service_started + container_name: web + ports: + - 8080:8080 + volumes: + - "./web-component/config:/app/config" + environment: + SPRING_DATASOURCE_URL: jdbc:mysql://iwp_mysql:3306/${DATABASE_NAME}?allowPublicKeyRetrieval=TRUE&useSSL=FALSE + SPRING_DATASOURCE_USERNAME: ${DATABASE_USERNAME} + SPRING_DATASOURCE_PASSWORD: ${DATABASE_PASSWORD} + SPRING_ACTIVEMQ_USER: ${ARTEMIS_USER} + SPRING_ACTIVEMQ_PASSWORD: ${ARTEMIS_PASSWORD} + JASYPT_ENCRYPTOR_PASSWORD: ${JASYPT_ENCRYPTOR_PASSWORD} + env_file: .env + platform: linux/amd64 + + iwp-acii: + image: ghcr.io/mitre/iat/iwp-acii:latest + container_name: acii + restart: always + depends_on: + - "activemq" + volumes: + - "./acii-component/config/acii.conf.template:/etc/iwp-acii/acii.conf.template:ro" + - "./acii-component/config/start-acii.sh:/etc/iwp-acii/start-acii.sh:ro" + environment: + ARTEMIS_USER: ${ARTEMIS_USER} + ARTEMIS_PASSWORD: ${ARTEMIS_PASSWORD} + command: ["sh", "/etc/iwp-acii/start-acii.sh"] + + iwp-biqt: + image: ghcr.io/mitre/iat/iwp-biqt:latest + container_name: biqt + restart: always + depends_on: + - "activemq" + volumes: + - "./biqt-component/config:/app/config" + environment: + spring.activemq.user: ${ARTEMIS_USER} + spring.activemq.password: ${ARTEMIS_PASSWORD} + platform: linux/amd64 + + # Iris Annotation via CPU + iwp-annotation: + image: ghcr.io/mitre/iat/iwp-annotation:latest + container_name: annotation + restart: always + depends_on: + - "activemq" + environment: + - STOMP_HOST=activemq:61613 + - STOMP_USER=${ARTEMIS_USER} + - STOMP_PASSWORD=${ARTEMIS_PASSWORD} + - STOMP_HEARTBEATS=10000,0 + platform: linux/amd64 + + # Iris Annotation via GPU + # iwp-annotation: + # image: ghcr.io/mitre/iat/iwp-annotation:latest + # container_name: annotation + # restart: always + # depends_on: + # - "activemq" + # environment: + # - STOMP_HOST=activemq:61613 + # - STOMP_USER=${ARTEMIS_USER:-artemis} + # - STOMP_PASSWORD=${ARTEMIS_PASSWORD} + # - STOMP_HEARTBEATS=10000,0 + # platform: linux/amd64 + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: all + # capabilities: [gpu] + + iwp-tshepii: + image: ghcr.io/mitre/iat/iwp-tshepii:latest + container_name: tshepii + restart: always + depends_on: + - "activemq" + volumes: + - "./tshepii-component/config/tshepii.conf.template:/etc/iwp-tshepii/tshepii.conf.template:ro" + - "./tshepii-component/config/start-tshepii.sh:/etc/iwp-tshepii/start-tshepii.sh:ro" + - "./tshepii-component/config/log4cxx.conf:/etc/iwp-tshepii/log4cxx.conf:ro" + environment: + ARTEMIS_USER: ${ARTEMIS_USER} + ARTEMIS_PASSWORD: ${ARTEMIS_PASSWORD} + command: ["sh", "/etc/iwp-tshepii/start-tshepii.sh"] + platform: linux/amd64 + + iwp-pdm: + image: ghcr.io/mitre/iat/iwp-pdm:latest + container_name: pdm + restart: always + depends_on: + - "activemq" + environment: + STOMP_HOST: activemq:61613 + ARTEMIS_USER: ${ARTEMIS_USER} + ARTEMIS_PASSWORD: ${ARTEMIS_PASSWORD} + STOMP_HEARTBEATS: 10000,0 + platform: linux/amd64 + + mysql: + image: mysql:8 + container_name: iwp_mysql + restart: always + environment: + MYSQL_DATABASE: ${DATABASE_NAME} + # Allows database access without using the root account. + MYSQL_USER: ${DATABASE_USERNAME} + # Password for the non-root database account. + MYSQL_PASSWORD: ${DATABASE_PASSWORD} + # Password for root access + MYSQL_ROOT_PASSWORD: ${DATABASE_ROOT_PASSWORD} + healthcheck: + test: mysqladmin ping -h localhost -u $$DATABASE_USERNAME --password=$$DATABASE_PASSWORD + interval: 1s + timeout: 1s + retries: 50 + expose: + # Exposes port 3306 within the Docker network. + - '3306' + # Persists database data. + volumes: + - my-db:/var/lib/mysql + env_file: .env + +volumes: + my-db: diff --git a/iris-workstation/iwp-simple/iwp-tshepii/config/log4cxx.conf b/docker/tshepii-component/config/log4cxx.conf similarity index 100% rename from iris-workstation/iwp-simple/iwp-tshepii/config/log4cxx.conf rename to docker/tshepii-component/config/log4cxx.conf diff --git a/docker/tshepii-component/config/start-tshepii.sh b/docker/tshepii-component/config/start-tshepii.sh new file mode 100644 index 0000000..88abd85 --- /dev/null +++ b/docker/tshepii-component/config/start-tshepii.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu + +sed \ + -e "s|\${ARTEMIS_USER}|${ARTEMIS_USER}|g" \ + -e "s|\${ARTEMIS_PASSWORD}|${ARTEMIS_PASSWORD}|g" \ + /etc/iwp-tshepii/tshepii.conf.template > /etc/iwp-tshepii/tshepii.conf + +exec tshepii /etc/iwp-tshepii/tshepii.conf /etc/iwp-tshepii/log4cxx.conf \ No newline at end of file diff --git a/docker/tshepii-component/config/tshepii.conf.template b/docker/tshepii-component/config/tshepii.conf.template new file mode 100755 index 0000000..29eee42 --- /dev/null +++ b/docker/tshepii-component/config/tshepii.conf.template @@ -0,0 +1,17 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +broker-URI=tcp://activemq:61616 +artemis-user=${ARTEMIS_USER} +artemis-password=${ARTEMIS_PASSWORD} +tshepii-request-queue=iris.tshepii.request +tshepii-response-queue=iris.tshepii.response +allowed-img-exts=PNG,JP2,BMP,PIXMAP +tshepii-output-path=/tmp/ \ No newline at end of file diff --git a/docker/web-component/config/application.properties b/docker/web-component/config/application.properties new file mode 100644 index 0000000..b3c53e1 --- /dev/null +++ b/docker/web-component/config/application.properties @@ -0,0 +1,139 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +# ActiveMQ services available +spring.activemq.broker-url=tcp://activemq:61616 +spring.activemq.user=${ARTEMIS_USER} +spring.activemq.password=${ARTEMIS_PASSWORD} +server.error.whitelabel.enabled=false + +iris.biqtService=true +iris.aciiService=true +iris.dmipaService=false +iris.tshepiiService=true +iris.dualPDMService=true +iris.contactService=true +iris.pdmService=true + +iris.aciiQueueRequest=iris.acii.request +iris.aciiQueueResponse=iris.acii.response +iris.biqtQueueRequest=iris.biqt.request +iris.biqtQueueResponse=iris.biqt.response +iris.tshepiiQueueRequest=iris.tshepii.request +iris.tshepiiQueueResponse=iris.tshepii.response +iris.contactQueueResponse=iris.contactdetection.response +iris.irisAnnotationQueueRequest=iris.annotation.request +iris.irisAnnotationQueueResponse=iris.annotation.response +iris.pdmQueueRequest=iris.pdm.request +iris.pdmQueueResponse=iris.pdm.response +iris.dualPDMQueueRequest=iris.dual.pdm.request +iris.dualPDMQueueResponse=iris.dual.pdm.response + +iris.aciiServiceName=acii +iris.biqtServiceName=biqt +iris.tshepiiServiceName=tshepii +iris.irisAnnotationServiceName=annotation +iris.dualPDMServiceName=dualpdm +iris.pdmServiceName=pdm + + +# Services addresses +iris.activeMqAddress=tcp://activemq:61616 +iris.iwpWebAddress=http://localhost:8080 +iris.activeMqHttpAddress=http://activemq:8161/console/jolokia/read/org.apache.activemq.artemis:broker=*,component=addresses,address=*,subcomponent=queues,*/ConsumerCount,MessagesAdded,MessagesAcknowledged +iris.activeMqUserName=${ARTEMIS_USER} +iris.activeMqPassword=${ARTEMIS_PASSWORD} + +#Spring Data +spring.jpa.hibernate.ddl-auto=update +spring.jpa.hibernate.use-new-id-generator-mappings=false +spring.datasource.url=jdbc:mysql://iwp_mysql:3306/${DATABASE_NAME}?allowPublicKeyRetrieval=TRUE&useSSL=FALSE +spring.datasource.username=${DATABASE_USERNAME} +spring.datasource.password=${DATABASE_PASSWORD} +spring.datasource.name=${DATABASE_NAME} +spring.jpa.show-sql=false +spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver + +# Security Options +# Whether the application should respect @Secured annotations and use a login page +iwp.security.secured=true + +# The following user-password hashes are encrypted and decrypted at runtime with the configured Jasypt password. +# Use this command to decrypt a value with the standalone Jasypt tool. To encrypt a value, replace `./decrypt.sh` with `./encrypt.sh` and set `input` to the value to encrypt. +# The additional options are required because Spring Jasypt uses these defaults for decryption. +# When encrypting a hash on the command line, escape `$` characters with a `\`. +#./decrypt.sh input="STRINGTODECRYPT" password="$JASYPT_ENCRYPTOR_PASSWORD" algorithm=PBEWITHHMACSHA512ANDAES_256 verbose=true stringOutputType=base64 providerName=SunJCE saltGeneratorClassName=org.jasypt.salt.RandomSaltGenerator ivGeneratorClassName=org.jasypt.iv.RandomIvGenerator +#./encrypt.sh input="STRINGTOENCRYPT" password="$JASYPT_ENCRYPTOR_PASSWORD" algorithm=PBEWITHHMACSHA512ANDAES_256 verbose=true stringOutputType=base64 providerName=SunJCE saltGeneratorClassName=org.jasypt.salt.RandomSaltGenerator ivGeneratorClassName=org.jasypt.iv.RandomIvGenerator + +# Download the tools from http://www.jasypt.org/cli.html. If the ZIP download is unavailable, see https://github.com/jasypt/jasypt/releases. + +iwp.users.defaultAdminFullName=${DEFAULT_ADMIN_FULL_NAME} +iwp.users.defaultAdminTitle=${DEFAULT_ADMIN_TITLE} +iwp.users.defaultAdminEmail=${DEFAULT_ADMIN_EMAIL} +iwp.users.defaultAdminUsername=${DEFAULT_USER_USERNAME} +iwp.users.defaultAdminPassword=${DEFAULT_USER_PASSWORD} + +iwp.users.defaultReviewerFullName=${DEFAULT_REVIEWER_FULL_NAME} +iwp.users.defaultReviewerTitle=${DEFAULT_REVIEWER_TITLE} +iwp.users.defaultReviewerEmail=${DEFAULT_REVIEWER_EMAIL} +iwp.users.defaultReviewerUsername=${DEFAULT_REVIEWER_USERNAME} +iwp.users.defaultReviewerPassword=${DEFAULT_REVIEWER_PASSWORD} + +iwp.users.defaultSupervisorFullName=${DEFAULT_SUPERVISOR_FULL_NAME} +iwp.users.defaultSupervisorTitle=${DEFAULT_SUPERVISOR_TITLE} +iwp.users.defaultSupervisorEmail=${DEFAULT_SUPERVISOR_EMAIL} +iwp.users.defaultSupervisorUsername=${DEFAULT_SUPERVISOR_USERNAME} +iwp.users.defaultSupervisorPassword=${DEFAULT_SUPERVISOR_PASSWORD} + +# File Uploads +iwp.upload.path=/app/uploads +iwp.image.path=/app/images + +# +# Iris Annotation Properties +# +iris.annotations.0=Unknown +iris.annotations.1=Iris +iris.annotations.2=Eyelash +iris.annotations.3=Sclera +iris.annotations.4=Pupil +iris.annotations.5=Eyebrow +iris.annotations.6=Highlight +iris.annotations.7=Border +iris.annotations.8=Crypt +iris.annotations.9=Ridge +iris.annotations.10=Contact + +iris.annotationsColorHex.0=#CCCCCC +iris.annotationsColorHex.1=#0000FF +iris.annotationsColorHex.2=#FF0000 +iris.annotationsColorHex.3=#FFFFFF +iris.annotationsColorHex.4=#000000 +iris.annotationsColorHex.5=#00FFFF +iris.annotationsColorHex.6=#FFFF00 +iris.annotationsColorHex.7=#FF00FF +iris.annotationsColorHex.8=#33FFFF +iris.annotationsColorHex.9=#FF9933 +iris.annotationsColorHex.10=#00A5FF + +iris.adjudicationValue.0=Match,true +iris.adjudicationValue.1=Exclude,false +iris.adjudicationValue.2=Inconclusive,true + +# +# Versioning properties +# +iris.biqtServiceVersion=5.0 +iris.aciiServiceVersion=6.0 +iris.tshepiiServiceVersion=3.0 +iris.irisAnnotationServiceVersion=2.0 +iris.pdmServiceVersion=7.0 +iris.iwpBackendVersion=8.0 +iris.iwpFrontendVersion=12.0 diff --git a/inference/README.md b/inference/README.md new file mode 100644 index 0000000..5d9bdad --- /dev/null +++ b/inference/README.md @@ -0,0 +1,71 @@ +> +> NOTICE +> +> This software (or technical data) was produced for the U. S. Government and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV (May 2014) - Alternative IV (Dec 2007). +> +> (c) 2024 The MITRE Corporation. All Rights Reserved. +> + +# Annotation Component +The Iris Annotation component is a service for the Iris Analysis Toolkit (IAT) that provides automated labeling for image segments. + +## Starting the Service +This service can be started in two ways: +1. (Recommended) [Docker Image via the IAT](#docker-and-iat) +2. [From a Script](#from-a-script) + +If you make changes to the scripts and want to create a new Docker image, see [Rebuild Docker Image](#rebuild-docker-image). + +### Docker and IAT +This is the recommended way. +To start the service with Docker through the IAT interface, follow the [Quickstart for Users](../README.md#quickstart-for-users) instructions. + +### From a Script +#### Prerequisites +- A Linux environment with Python 3.12 and the CPU or GPU Python dependencies installed. The pinned dependency lists are in `core/requirements/`. +- The Iris Annotation application files installed in `/usr/share/iris-annotation/`, including `mq-main.py`, `main.py`, the `segment/` directory, and the generated `*_pb2.py` files. +- Model checkpoint files installed in `/usr/share/models`. The supplied `6248.pth` checkpoint is expected in this directory. +- A writable `/mnt/host/tmp` directory. The service temporarily stores image data received in requests there. +- An ActiveMQ Artemis-compatible broker reachable over STOMP. By default, the service connects to `localhost:61613`. +- For GPU inference, a CUDA-compatible PyTorch runtime and `USE_CUDA=True`. + For CPU inference, leave `USE_CUDA` unset or set it to `False`. + +#### Starting the Script +1. Start an ActiveMQ Artemis-compatible broker with STOMP enabled. +2. Set the broker connection environment variables. `STOMP_HOST` defaults to `localhost:61613`; provide `STOMP_USER` and `STOMP_PASSWORD` when the broker requires authentication. +3. Start the service: + ```sh + export STOMP_HOST="localhost:61613" + export STOMP_USER="artemis" + export STOMP_PASSWORD="your-password" + export USE_CUDA="False" + + python3 /usr/share/iris-annotation/mq-main.py + ``` + +The service consumes protobuf `ImageServiceQuery` messages from `iris.annotation.request` and sends `IrisAnnotationReply` messages to `iris.annotation.response`. Failed requests are sent to `iris.annotation.response-failures`. + +*NOTE: This option is not recommended since a client must publish an `ImageServiceQuery` message to ActiveMQ on `iris.annotation.request` and consume the resulting `IrisAnnotationReply` message from `iris.annotation.response`. A client script is not currently provided. + +## Rebuild Docker Image +This service can use a CPU or GPU, based on the `USE_CUDA` environment variable set in the Dockerfile. The CPU and GPU Dockerfiles install different dependency versions. **Do not change `USE_CUDA` in these files.** To build a new Docker image from the `inference/` subdirectory, use the applicable command below. Apple Silicon users should retain the `--platform linux/amd64` option for the CPU build. + CPU Version: + ```sh + cd core + + # Add --platform linux/amd64 for Apple Silicon Systems + # Remove `--secret` argument if organization certificate does not need to be installed. + docker build --platform linux/amd64 -t new_inference-cpu:YOUR_TAG_HERE \ + --secret id=ca_file,src=ca_file.crt \ + -f Dockerfile-CPU . + ``` + + GPU Version: + ```sh + cd core + + # Remove `--secret` argument if organization certificate does not need to be installed. + docker build -t new_inference_gpu:YOUR_TAG_HERE \ + --secret id=ca_file,src=ca_file.crt \ + -f Dockerfile-GPU . + ``` diff --git a/inference/core/Dockerfile-CPU b/inference/core/Dockerfile-CPU new file mode 100755 index 0000000..27b36fa --- /dev/null +++ b/inference/core/Dockerfile-CPU @@ -0,0 +1,74 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) - Alternative IV (Dec 2007) +# +# (c) 2026 The MITRE Corporation. All Rights Reserved. +# + +FROM debian:bookworm-slim AS base + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + bzip2 build-essential ca-certificates curl wget python3-pip \ + libjpeg-dev libpng-dev libtiff-dev \ + libc-dev libc-dev-bin linux-libc-dev \ + zlib1g-dev libbz2-dev liblzma-dev unzip && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Building on some infrastructure requires the installation of a certificate authority. This is OPTIONAL +# for external systems and will only be installed when the ca_file secret is provided in the build command. +RUN --mount=type=secret,id=ca_file,required=false \ + if [ -f /run/secrets/ca_file ]; then \ + cat /run/secrets/ca_file >> /etc/ssl/certs/ca-certificates.crt; \ + fi + +RUN wget https://repo.anaconda.com/miniconda/Miniconda3-py312_24.1.2-0-Linux-x86_64.sh && \ + bash Miniconda3-py312_24.1.2-0-Linux-x86_64.sh -b && \ + rm Miniconda3-py312_24.1.2-0-Linux-x86_64.sh + +ENV PATH="/root/miniconda3/bin:${PATH}" + +RUN conda config --set ssl_verify /etc/ssl/certs/ca-certificates.crt && \ + conda config --append channels pytorch && \ + conda config --append channels conda-forge && \ + conda update -y conda && \ + conda --version && \ + python3 --version + +COPY requirements/conda_cpu.txt /tmp/ +RUN conda install -y --file /tmp/conda_cpu.txt && \ + conda clean --all -f -y + +COPY requirements/pip_cpu.txt /tmp/ +RUN pip3 install --upgrade pip setuptools && \ + pip3 install --no-cache-dir -r /tmp/pip_cpu.txt + +############################################################### + +FROM debian:bookworm-slim + +ENV PATH="/root/miniconda3/bin:${PATH}" +ENV USE_CUDA="False" + +COPY --from=base /root/miniconda3 /root/miniconda3 + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates curl libjpeg-dev libpng-dev libtiff-dev \ + libc-dev libc-dev-bin linux-libc-dev && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /usr/share/iris-annotation + +COPY src/segment ./segment +COPY src/config.py src/utility.py src/main.py src/mq-main.py src/Messages_pb2.py src/IrisAnnotation_pb2.py ./ +COPY src/models /usr/share/models + +RUN mkdir -p /mnt/host/input /mnt/host/dataset /mnt/host/tmp + +CMD [ "python3", "/usr/share/iris-annotation/mq-main.py" ] diff --git a/inference/core/Dockerfile-GPU b/inference/core/Dockerfile-GPU new file mode 100755 index 0000000..5b42b28 --- /dev/null +++ b/inference/core/Dockerfile-GPU @@ -0,0 +1,73 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04 AS base + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + build-essential ca-certificates wget python3-pip \ + libjpeg-dev libpng-dev libtiff-dev libc-dev libc-dev-bin linux-libc-dev unzip && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Building on some infrastructure requires the installation of a certificate authority. This is OPTIONAL +# for external systems and will only be installed when the ca_file secret is provided in the build command. +RUN --mount=type=secret,id=ca_file,required=false \ + if [ -f /run/secrets/ca_file ]; then \ + cp /run/secrets/ca_file /usr/local/share/ca-certificates/mitre-ba-root.crt; \ + update-ca-certificates; \ + fi + +RUN wget https://repo.anaconda.com/miniconda/Miniconda3-py312_24.1.2-0-Linux-x86_64.sh && \ + bash Miniconda3-py312_24.1.2-0-Linux-x86_64.sh -b -p /root/miniconda3 && \ + rm Miniconda3-py312_24.1.2-0-Linux-x86_64.sh + +ENV PATH="/root/miniconda3/bin:${PATH}" + +RUN conda config --set ssl_verify /etc/ssl/certs/ca-certificates.crt && \ + conda config --append channels pytorch && \ + conda config --append channels conda-forge && \ + conda update -y conda && \ + conda --version && \ + python3 --version + +COPY requirements/conda_gpu.txt /tmp/ +RUN conda install -y --file /tmp/conda_gpu.txt && \ + conda clean --all + +COPY requirements/pip_gpu.txt /tmp/ +RUN pip3 install --upgrade pip && \ + pip3 install --no-cache-dir -r /tmp/pip_gpu.txt + +############################################################### + +FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04 + +ENV USE_CUDA="True" + +ENV PATH="/root/miniconda3/bin:${PATH}" + +COPY --from=base /root/miniconda3/. /root/miniconda3/ + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates libjpeg-dev libpng-dev libtiff-dev libc-dev libc-dev-bin linux-libc-dev && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /usr/share/iris-annotation + +COPY src/segment ./segment +COPY src/config.py src/utility.py src/main.py src/mq-main.py src/Messages_pb2.py src/IrisAnnotation_pb2.py ./ +COPY src/models /usr/share/models + +RUN mkdir -p /mnt/host && mkdir /mnt/host/input && mkdir /mnt/host/dataset && mkdir /mnt/host/tmp + +CMD [ "python3", "/usr/share/iris-annotation/mq-main.py" ] diff --git a/inference/core/requirements/conda_cpu.txt b/inference/core/requirements/conda_cpu.txt new file mode 100755 index 0000000..280c322 --- /dev/null +++ b/inference/core/requirements/conda_cpu.txt @@ -0,0 +1,18 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +docopt==0.6.2 +glib==2.78.4 +intel-openmp=2023.1.0 +mkl=2023.1.0 +pytorch=2.2.2=py3.12_cpu_0 +pytorch-mutex=1.0=cpu +stomp.py==8.0.1 +torchvision=0.17.2=py312_cpu diff --git a/inference/core/requirements/conda_gpu.txt b/inference/core/requirements/conda_gpu.txt new file mode 100755 index 0000000..50cf7b4 --- /dev/null +++ b/inference/core/requirements/conda_gpu.txt @@ -0,0 +1,14 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +docopt==0.6.2 +glib==2.78.4 +pytorch==2.2.0 +stomp.py==8.0.1 \ No newline at end of file diff --git a/inference/core/requirements/pip_cpu.txt b/inference/core/requirements/pip_cpu.txt new file mode 100755 index 0000000..8afffc9 --- /dev/null +++ b/inference/core/requirements/pip_cpu.txt @@ -0,0 +1,19 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2026 The MITRE Corporation. All Rights Reserved. +# + +opencv-python-headless==4.9.0.80 +Pillow-SIMD +scikit-image==0.23.1 +scikit-learn==1.4.2 +scipy==1.13.0 +six==1.16.0 +tensorboardx==2.6.2.2 +protobuf==3.20.3 +numpy==1.26.4 diff --git a/inference/core/requirements/pip_gpu.txt b/inference/core/requirements/pip_gpu.txt new file mode 100755 index 0000000..8a84424 --- /dev/null +++ b/inference/core/requirements/pip_gpu.txt @@ -0,0 +1,20 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2026 The MITRE Corporation. All Rights Reserved. +# + +opencv-python-headless==4.9.0.80 +Pillow-SIMD==9.0.0.post1 +scikit-image==0.23.1 +scikit-learn==1.4.2 +scipy==1.13.0 +six==1.16.0 +tensorboardx==2.6.2.2 +torchvision==0.17.2 +protobuf==3.20.3 +numpy==1.26.4 diff --git a/inference/core/src/IrisAnnotation_pb2.py b/inference/core/src/IrisAnnotation_pb2.py new file mode 100644 index 0000000..fd51dbb --- /dev/null +++ b/inference/core/src/IrisAnnotation_pb2.py @@ -0,0 +1,507 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: IrisAnnotation.proto + +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='IrisAnnotation.proto', + package='org.mitre.iwp.buffers', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n\x14IrisAnnotation.proto\x12\x15org.mitre.iwp.buffers\"\x8e\x02\n\x13IrisAnnotationQuery\x12\n\n\x02id\x18\x01 \x01(\t\x12-\n\x04mode\x18\x02 \x01(\x0e\x32\x1f.org.mitre.iwp.buffers.ModeType\x12\x11\n\timagePath\x18\x03 \x01(\t\x12\x12\n\nimageBytes\x18\x04 \x01(\x0c\x12\x12\n\nimagesFile\x18\x05 \x01(\t\x12G\n\x13optimizationOptions\x18\x06 \x01(\x0b\x32*.org.mitre.iwp.buffers.OptimizationOptions\x12\x38\n\x0c\x63lassOptions\x18\x07 \x03(\x0b\x32\".org.mitre.iwp.buffers.ClassOption\"(\n\x13OptimizationOptions\x12\x11\n\tbatchSize\x18\x01 \x01(\x05\"8\n\x0b\x43lassOption\x12\r\n\x05index\x18\x01 \x01(\x05\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\t\"\xae\x01\n\x13IrisAnnotationReply\x12\n\n\x02id\x18\x01 \x01(\t\x12\x10\n\x08response\x18\x02 \x01(\t\x12\x14\n\x0cresponseCode\x18\x03 \x01(\t\x12\x17\n\x0fresponseMessage\x18\x04 \x01(\t\x12\x16\n\x0eresponseTimeMS\x18\x05 \x01(\t\x12\x32\n\x07results\x18\x06 \x03(\x0b\x32!.org.mitre.iwp.buffers.Annotation\"\\\n\nAnnotation\x12\n\n\x02id\x18\x01 \x01(\t\x12\x10\n\x08\x63olorHex\x18\x02 \x01(\t\x12\x30\n\x08polygons\x18\x03 \x03(\x0b\x32\x1e.org.mitre.iwp.buffers.Polygon\"h\n\x07Polygon\x12.\n\x07outline\x18\x01 \x01(\x0b\x32\x1d.org.mitre.iwp.buffers.Points\x12-\n\x06inline\x18\x02 \x03(\x0b\x32\x1d.org.mitre.iwp.buffers.Points\"6\n\x06Points\x12,\n\x06points\x18\x01 \x03(\x0b\x32\x1c.org.mitre.iwp.buffers.Point\"\x1d\n\x05Point\x12\t\n\x01x\x18\x01 \x01(\x05\x12\t\n\x01y\x18\x02 \x01(\x05*$\n\x08ModeType\x12\n\n\x06single\x10\x00\x12\x0c\n\x08multiple\x10\x01\x62\x06proto3' +) + +_MODETYPE = _descriptor.EnumDescriptor( + name='ModeType', + full_name='org.mitre.iwp.buffers.ModeType', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='single', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='multiple', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=884, + serialized_end=920, +) +_sym_db.RegisterEnumDescriptor(_MODETYPE) + +ModeType = enum_type_wrapper.EnumTypeWrapper(_MODETYPE) +single = 0 +multiple = 1 + + + +_IRISANNOTATIONQUERY = _descriptor.Descriptor( + name='IrisAnnotationQuery', + full_name='org.mitre.iwp.buffers.IrisAnnotationQuery', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='org.mitre.iwp.buffers.IrisAnnotationQuery.id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='mode', full_name='org.mitre.iwp.buffers.IrisAnnotationQuery.mode', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='imagePath', full_name='org.mitre.iwp.buffers.IrisAnnotationQuery.imagePath', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='imageBytes', full_name='org.mitre.iwp.buffers.IrisAnnotationQuery.imageBytes', index=3, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='imagesFile', full_name='org.mitre.iwp.buffers.IrisAnnotationQuery.imagesFile', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='optimizationOptions', full_name='org.mitre.iwp.buffers.IrisAnnotationQuery.optimizationOptions', index=5, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='classOptions', full_name='org.mitre.iwp.buffers.IrisAnnotationQuery.classOptions', index=6, + number=7, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=48, + serialized_end=318, +) + + +_OPTIMIZATIONOPTIONS = _descriptor.Descriptor( + name='OptimizationOptions', + full_name='org.mitre.iwp.buffers.OptimizationOptions', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='batchSize', full_name='org.mitre.iwp.buffers.OptimizationOptions.batchSize', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=320, + serialized_end=360, +) + + +_CLASSOPTION = _descriptor.Descriptor( + name='ClassOption', + full_name='org.mitre.iwp.buffers.ClassOption', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='index', full_name='org.mitre.iwp.buffers.ClassOption.index', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='key', full_name='org.mitre.iwp.buffers.ClassOption.key', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='value', full_name='org.mitre.iwp.buffers.ClassOption.value', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=362, + serialized_end=418, +) + + +_IRISANNOTATIONREPLY = _descriptor.Descriptor( + name='IrisAnnotationReply', + full_name='org.mitre.iwp.buffers.IrisAnnotationReply', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='org.mitre.iwp.buffers.IrisAnnotationReply.id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='response', full_name='org.mitre.iwp.buffers.IrisAnnotationReply.response', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='responseCode', full_name='org.mitre.iwp.buffers.IrisAnnotationReply.responseCode', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='responseMessage', full_name='org.mitre.iwp.buffers.IrisAnnotationReply.responseMessage', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='responseTimeMS', full_name='org.mitre.iwp.buffers.IrisAnnotationReply.responseTimeMS', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='results', full_name='org.mitre.iwp.buffers.IrisAnnotationReply.results', index=5, + number=6, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=421, + serialized_end=595, +) + + +_ANNOTATION = _descriptor.Descriptor( + name='Annotation', + full_name='org.mitre.iwp.buffers.Annotation', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='org.mitre.iwp.buffers.Annotation.id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='colorHex', full_name='org.mitre.iwp.buffers.Annotation.colorHex', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='polygons', full_name='org.mitre.iwp.buffers.Annotation.polygons', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=597, + serialized_end=689, +) + + +_POLYGON = _descriptor.Descriptor( + name='Polygon', + full_name='org.mitre.iwp.buffers.Polygon', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='outline', full_name='org.mitre.iwp.buffers.Polygon.outline', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='inline', full_name='org.mitre.iwp.buffers.Polygon.inline', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=691, + serialized_end=795, +) + + +_POINTS = _descriptor.Descriptor( + name='Points', + full_name='org.mitre.iwp.buffers.Points', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='points', full_name='org.mitre.iwp.buffers.Points.points', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=797, + serialized_end=851, +) + + +_POINT = _descriptor.Descriptor( + name='Point', + full_name='org.mitre.iwp.buffers.Point', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='x', full_name='org.mitre.iwp.buffers.Point.x', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='y', full_name='org.mitre.iwp.buffers.Point.y', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=853, + serialized_end=882, +) + +_IRISANNOTATIONQUERY.fields_by_name['mode'].enum_type = _MODETYPE +_IRISANNOTATIONQUERY.fields_by_name['optimizationOptions'].message_type = _OPTIMIZATIONOPTIONS +_IRISANNOTATIONQUERY.fields_by_name['classOptions'].message_type = _CLASSOPTION +_IRISANNOTATIONREPLY.fields_by_name['results'].message_type = _ANNOTATION +_ANNOTATION.fields_by_name['polygons'].message_type = _POLYGON +_POLYGON.fields_by_name['outline'].message_type = _POINTS +_POLYGON.fields_by_name['inline'].message_type = _POINTS +_POINTS.fields_by_name['points'].message_type = _POINT +DESCRIPTOR.message_types_by_name['IrisAnnotationQuery'] = _IRISANNOTATIONQUERY +DESCRIPTOR.message_types_by_name['OptimizationOptions'] = _OPTIMIZATIONOPTIONS +DESCRIPTOR.message_types_by_name['ClassOption'] = _CLASSOPTION +DESCRIPTOR.message_types_by_name['IrisAnnotationReply'] = _IRISANNOTATIONREPLY +DESCRIPTOR.message_types_by_name['Annotation'] = _ANNOTATION +DESCRIPTOR.message_types_by_name['Polygon'] = _POLYGON +DESCRIPTOR.message_types_by_name['Points'] = _POINTS +DESCRIPTOR.message_types_by_name['Point'] = _POINT +DESCRIPTOR.enum_types_by_name['ModeType'] = _MODETYPE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +IrisAnnotationQuery = _reflection.GeneratedProtocolMessageType('IrisAnnotationQuery', (_message.Message,), { + 'DESCRIPTOR' : _IRISANNOTATIONQUERY, + '__module__' : 'IrisAnnotation_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.IrisAnnotationQuery) + }) +_sym_db.RegisterMessage(IrisAnnotationQuery) + +OptimizationOptions = _reflection.GeneratedProtocolMessageType('OptimizationOptions', (_message.Message,), { + 'DESCRIPTOR' : _OPTIMIZATIONOPTIONS, + '__module__' : 'IrisAnnotation_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.OptimizationOptions) + }) +_sym_db.RegisterMessage(OptimizationOptions) + +ClassOption = _reflection.GeneratedProtocolMessageType('ClassOption', (_message.Message,), { + 'DESCRIPTOR' : _CLASSOPTION, + '__module__' : 'IrisAnnotation_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.ClassOption) + }) +_sym_db.RegisterMessage(ClassOption) + +IrisAnnotationReply = _reflection.GeneratedProtocolMessageType('IrisAnnotationReply', (_message.Message,), { + 'DESCRIPTOR' : _IRISANNOTATIONREPLY, + '__module__' : 'IrisAnnotation_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.IrisAnnotationReply) + }) +_sym_db.RegisterMessage(IrisAnnotationReply) + +Annotation = _reflection.GeneratedProtocolMessageType('Annotation', (_message.Message,), { + 'DESCRIPTOR' : _ANNOTATION, + '__module__' : 'IrisAnnotation_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.Annotation) + }) +_sym_db.RegisterMessage(Annotation) + +Polygon = _reflection.GeneratedProtocolMessageType('Polygon', (_message.Message,), { + 'DESCRIPTOR' : _POLYGON, + '__module__' : 'IrisAnnotation_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.Polygon) + }) +_sym_db.RegisterMessage(Polygon) + +Points = _reflection.GeneratedProtocolMessageType('Points', (_message.Message,), { + 'DESCRIPTOR' : _POINTS, + '__module__' : 'IrisAnnotation_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.Points) + }) +_sym_db.RegisterMessage(Points) + +Point = _reflection.GeneratedProtocolMessageType('Point', (_message.Message,), { + 'DESCRIPTOR' : _POINT, + '__module__' : 'IrisAnnotation_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.Point) + }) +_sym_db.RegisterMessage(Point) + + +# @@protoc_insertion_point(module_scope) diff --git a/inference/core/src/Messages_pb2.py b/inference/core/src/Messages_pb2.py new file mode 100644 index 0000000..c6905c1 --- /dev/null +++ b/inference/core/src/Messages_pb2.py @@ -0,0 +1,1343 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: Messages.proto + +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='Messages.proto', + package='org.mitre.iwp.buffers', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n\x0eMessages.proto\x12\x15org.mitre.iwp.buffers\"\x7f\n\x0fIrisImageBuffer\x12\x16\n\x0e\x63orrelation_id\x18\x01 \x01(\t\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x0f\n\x07message\x18\x03 \x01(\t\x12\x32\n\x05image\x18\xe7\x07 \x01(\x0b\x32\".org.mitre.iwp.buffers.ImageBuffer\"\x8b\x01\n\x13\x43\x61ndidateListBuffer\x12\x16\n\x0e\x63orrelation_id\x18\x01 \x01(\t\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x0f\n\x07message\x18\x03 \x01(\t\x12:\n\ncandidates\x18\x04 \x03(\x0b\x32&.org.mitre.iwp.buffers.CandidateBuffer\"\xdb\x03\n\x0f\x43\x61ndidateBuffer\x12\x12\n\nsubject_id\x18\x01 \x01(\t\x12\x13\n\x0bmaster_name\x18\x02 \x01(\t\x12 \n\x18\x62iometric_set_identifier\x18\x03 \x01(\t\x12\x12\n\nimage_type\x18\x04 \x01(\t\x12+\n#friction_ridge_generalized_position\x18\x05 \x01(\t\x12!\n\x19print_position_descriptor\x18\x06 \x01(\t\x12\x13\n\x0bmatch_score\x18\x07 \x01(\x01\x12!\n\x19\x62iometric_image_available\x18\x08 \x01(\t\x12%\n\x1dname_of_designated_repository\x18\t \x01(\t\x12)\n!information_designation_character\x18\n \x01(\t\x12\x0c\n\x04note\x18\x0b \x01(\t\x12\x0c\n\x04pose\x18\x0c \x01(\t\x12\x10\n\x08smt_code\x18\r \x01(\t\x12\x11\n\teye_label\x18\x0e \x01(\t\x12\x39\n\x0cimage_buffer\x18\xe7\x07 \x01(\x0b\x32\".org.mitre.iwp.buffers.ImageBuffer\x12\x13\n\nimage_path\x18\xcf\x0f \x01(\t\"\x1b\n\x0bImageBuffer\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"\x81\x02\n\x11ImageServiceQuery\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\x12\x12\n\nimageBytes\x18\x03 \x01(\x0c\x12\x45\n\timageType\x18\x04 \x01(\x0e\x32\x32.org.mitre.iwp.buffers.ImageServiceQuery.ImageType\x12\r\n\x05width\x18\x05 \x01(\x05\x12\x0e\n\x06height\x18\x06 \x01(\x05\"X\n\tImageType\x12\x07\n\x03PNG\x10\x00\x12\x07\n\x03JP2\x10\x02\x12\x07\n\x03\x42MP\x10\x03\x12\n\n\x06PIXMAP\x10\x08\x12\x07\n\x03JPG\x10\n\x12\x08\n\x04JPEG\x10\x0b\x12\x07\n\x03TIF\x10\r\x12\x08\n\x04TIFF\x10\x0e\"\x8d\x01\n\x15TShepiiCompareRequest\x12\x39\n\tresponse1\x18\x01 \x01(\x0b\x32&.org.mitre.iwp.buffers.TShepiiResponse\x12\x39\n\tresponse2\x18\x02 \x01(\x0b\x32&.org.mitre.iwp.buffers.TShepiiResponse\"\xcd\x04\n\x0cServiceReply\x12\n\n\x02id\x18\x01 \x01(\t\x12\x39\n\x0c\x61\x63iiResponse\x18\x02 \x01(\x0b\x32#.org.mitre.iwp.buffers.AciiResponse\x12\x39\n\x0c\x62iqtResponse\x18\x03 \x01(\x0b\x32#.org.mitre.iwp.buffers.BiqtResponse\x12\x43\n\x0fserviceResponse\x18\x04 \x01(\x0e\x32*.org.mitre.iwp.buffers.ServiceResponseType\x12\x0f\n\x07imageId\x18\x05 \x01(\t\x12\x11\n\timagePath\x18\x06 \x01(\t\x12\x41\n\x10\x63ontrastResponse\x18\x07 \x01(\x0b\x32\'.org.mitre.iwp.buffers.ContrastResponse\x12=\n\rcryptResponse\x18\x08 \x01(\x0b\x32&.org.mitre.iwp.buffers.TShepiiResponse\x12>\n\x0e\x63ryptResponse2\x18\t \x01(\x0b\x32&.org.mitre.iwp.buffers.TShepiiResponse\x12;\n\rdmipaResponse\x18\n \x01(\x0b\x32$.org.mitre.iwp.buffers.DmipaResponse\x12S\n\x19\x63ontactclassifierResponse\x18\x0b \x01(\x0b\x32\x30.org.mitre.iwp.buffers.ContactClassifierResponse\"\x8a\x01\n\x0c\x41\x63iiResponse\x12>\n\nhorizontal\x18\x02 \x01(\x0e\x32*.org.mitre.iwp.buffers.HorizontalAlignment\x12:\n\x08vertical\x18\x03 \x01(\x0e\x32(.org.mitre.iwp.buffers.VerticalAlignment\"\xb6\x01\n\x0c\x42iqtResponse\x12\x10\n\x08\x63ontrast\x18\x01 \x01(\x05\x12\x14\n\x0cirisDiameter\x18\x02 \x01(\x05\x12\x1a\n\x12percentVisibleIris\x18\x03 \x01(\x05\x12\x0f\n\x07quality\x18\x04 \x01(\x05\x12\x13\n\x0bimageHeight\x18\x05 \x01(\x05\x12\x12\n\nimageWidth\x18\x06 \x01(\x05\x12\x13\n\x0birisCenterX\x18\x07 \x01(\x05\x12\x13\n\x0birisCenterY\x18\x08 \x01(\x05\"&\n\x10\x43ontrastResponse\x12\x12\n\nimageBytes\x18\x01 \x01(\x0c\"\x8a\x03\n\x0fTShepiiResponse\x12\x30\n\tkeypoints\x18\x01 \x03(\x0b\x32\x1d.org.mitre.iwp.buffers.TPoint\x12,\n\x06\x63rypts\x18\x02 \x03(\x0b\x32\x1c.org.mitre.iwp.buffers.Crypt\x12\x12\n\nmatchIndex\x18\x07 \x03(\x05\x12\x30\n\x08\x63ropIris\x18\x03 \x01(\x0b\x32\x1e.org.mitre.iwp.buffers.TCircle\x12\x31\n\tcropPupil\x18\x04 \x01(\x0b\x32\x1e.org.mitre.iwp.buffers.TCircle\x12\x34\n\x0coriginalIris\x18\x05 \x01(\x0b\x32\x1e.org.mitre.iwp.buffers.TCircle\x12\x35\n\roriginalPupil\x18\x06 \x01(\x0b\x32\x1e.org.mitre.iwp.buffers.TCircle\x12\x17\n\x0finputImageWidth\x18\x08 \x01(\x05\x12\x18\n\x10inputImageHeight\x18\t \x01(\x05\"H\n\x07TCircle\x12-\n\x06\x63\x65nter\x18\x01 \x01(\x0b\x32\x1d.org.mitre.iwp.buffers.TPoint\x12\x0e\n\x06radius\x18\x02 \x01(\x01\",\n\x06TPoint\x12\t\n\x01x\x18\x01 \x01(\x01\x12\t\n\x01y\x18\x02 \x01(\x01\x12\x0c\n\x04size\x18\x03 \x01(\x01\"6\n\x05\x43rypt\x12-\n\x06points\x18\x01 \x03(\x0b\x32\x1d.org.mitre.iwp.buffers.TPoint\"\x99\x01\n\rDmipaResponse\x12\x10\n\x08\x66ilename\x18\x01 \x01(\t\x12\x14\n\x0cirisDetected\x18\x02 \x01(\x08\x12\x10\n\x08response\x18\x03 \x01(\t\x12\x0f\n\x07message\x18\x04 \x01(\t\x12\x0f\n\x07tmpPath\x18\x05 \x01(\t\x12\x15\n\rdetectionCode\x18\x06 \x01(\t\x12\x15\n\rprocessTimeMS\x18\x07 \x01(\t\"}\n\x19\x43ontactClassifierResponse\x12\x10\n\x08response\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x0f\n\x07tmpPath\x18\x03 \x01(\t\x12\x15\n\rdetectionCode\x18\x04 \x01(\t\x12\x15\n\rprocessTimeMS\x18\x05 \x01(\t*A\n\x13HorizontalAlignment\x12\x15\n\x11UnknownHorizontal\x10\x00\x12\x08\n\x04Left\x10\x01\x12\t\n\x05Right\x10\x02*:\n\x11VerticalAlignment\x12\x13\n\x0fUnknownVertical\x10\x00\x12\x06\n\x02Up\x10\x01\x12\x08\n\x04\x44own\x10\x02*\xdc\x01\n\x13ServiceResponseType\x12\x1a\n\x16UnknownServiceResponse\x10\x00\x12\x17\n\x13\x41\x63iiServiceResponse\x10\x01\x12\x17\n\x13\x42iqtServiceResponse\x10\x02\x12\x18\n\x14\x44mipaServiceResponse\x10\x03\x12\x1b\n\x17\x43ontrastServiceResponse\x10\x04\x12\x1a\n\x16TShepiiServiceResponse\x10\x05\x12$\n ContactClassifierServiceResponse\x10\x06\x62\x06proto3' +) + +_HORIZONTALALIGNMENT = _descriptor.EnumDescriptor( + name='HorizontalAlignment', + full_name='org.mitre.iwp.buffers.HorizontalAlignment', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='UnknownHorizontal', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='Left', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='Right', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=3037, + serialized_end=3102, +) +_sym_db.RegisterEnumDescriptor(_HORIZONTALALIGNMENT) + +HorizontalAlignment = enum_type_wrapper.EnumTypeWrapper(_HORIZONTALALIGNMENT) +_VERTICALALIGNMENT = _descriptor.EnumDescriptor( + name='VerticalAlignment', + full_name='org.mitre.iwp.buffers.VerticalAlignment', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='UnknownVertical', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='Up', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='Down', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=3104, + serialized_end=3162, +) +_sym_db.RegisterEnumDescriptor(_VERTICALALIGNMENT) + +VerticalAlignment = enum_type_wrapper.EnumTypeWrapper(_VERTICALALIGNMENT) +_SERVICERESPONSETYPE = _descriptor.EnumDescriptor( + name='ServiceResponseType', + full_name='org.mitre.iwp.buffers.ServiceResponseType', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='UnknownServiceResponse', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='AciiServiceResponse', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='BiqtServiceResponse', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='DmipaServiceResponse', index=3, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ContrastServiceResponse', index=4, number=4, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TShepiiServiceResponse', index=5, number=5, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ContactClassifierServiceResponse', index=6, number=6, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=3165, + serialized_end=3385, +) +_sym_db.RegisterEnumDescriptor(_SERVICERESPONSETYPE) + +ServiceResponseType = enum_type_wrapper.EnumTypeWrapper(_SERVICERESPONSETYPE) +UnknownHorizontal = 0 +Left = 1 +Right = 2 +UnknownVertical = 0 +Up = 1 +Down = 2 +UnknownServiceResponse = 0 +AciiServiceResponse = 1 +BiqtServiceResponse = 2 +DmipaServiceResponse = 3 +ContrastServiceResponse = 4 +TShepiiServiceResponse = 5 +ContactClassifierServiceResponse = 6 + + +_IMAGESERVICEQUERY_IMAGETYPE = _descriptor.EnumDescriptor( + name='ImageType', + full_name='org.mitre.iwp.buffers.ImageServiceQuery.ImageType', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='PNG', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='JP2', index=1, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='BMP', index=2, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='PIXMAP', index=3, number=8, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='JPG', index=4, number=10, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='JPEG', index=5, number=11, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TIF', index=6, number=13, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TIFF', index=7, number=14, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=989, + serialized_end=1077, +) +_sym_db.RegisterEnumDescriptor(_IMAGESERVICEQUERY_IMAGETYPE) + + +_IRISIMAGEBUFFER = _descriptor.Descriptor( + name='IrisImageBuffer', + full_name='org.mitre.iwp.buffers.IrisImageBuffer', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='correlation_id', full_name='org.mitre.iwp.buffers.IrisImageBuffer.correlation_id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='success', full_name='org.mitre.iwp.buffers.IrisImageBuffer.success', index=1, + number=2, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='message', full_name='org.mitre.iwp.buffers.IrisImageBuffer.message', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='image', full_name='org.mitre.iwp.buffers.IrisImageBuffer.image', index=3, + number=999, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=41, + serialized_end=168, +) + + +_CANDIDATELISTBUFFER = _descriptor.Descriptor( + name='CandidateListBuffer', + full_name='org.mitre.iwp.buffers.CandidateListBuffer', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='correlation_id', full_name='org.mitre.iwp.buffers.CandidateListBuffer.correlation_id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='success', full_name='org.mitre.iwp.buffers.CandidateListBuffer.success', index=1, + number=2, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='message', full_name='org.mitre.iwp.buffers.CandidateListBuffer.message', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='candidates', full_name='org.mitre.iwp.buffers.CandidateListBuffer.candidates', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=171, + serialized_end=310, +) + + +_CANDIDATEBUFFER = _descriptor.Descriptor( + name='CandidateBuffer', + full_name='org.mitre.iwp.buffers.CandidateBuffer', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='subject_id', full_name='org.mitre.iwp.buffers.CandidateBuffer.subject_id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='master_name', full_name='org.mitre.iwp.buffers.CandidateBuffer.master_name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='biometric_set_identifier', full_name='org.mitre.iwp.buffers.CandidateBuffer.biometric_set_identifier', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='image_type', full_name='org.mitre.iwp.buffers.CandidateBuffer.image_type', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='friction_ridge_generalized_position', full_name='org.mitre.iwp.buffers.CandidateBuffer.friction_ridge_generalized_position', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='print_position_descriptor', full_name='org.mitre.iwp.buffers.CandidateBuffer.print_position_descriptor', index=5, + number=6, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='match_score', full_name='org.mitre.iwp.buffers.CandidateBuffer.match_score', index=6, + number=7, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='biometric_image_available', full_name='org.mitre.iwp.buffers.CandidateBuffer.biometric_image_available', index=7, + number=8, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='name_of_designated_repository', full_name='org.mitre.iwp.buffers.CandidateBuffer.name_of_designated_repository', index=8, + number=9, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='information_designation_character', full_name='org.mitre.iwp.buffers.CandidateBuffer.information_designation_character', index=9, + number=10, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='note', full_name='org.mitre.iwp.buffers.CandidateBuffer.note', index=10, + number=11, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='pose', full_name='org.mitre.iwp.buffers.CandidateBuffer.pose', index=11, + number=12, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='smt_code', full_name='org.mitre.iwp.buffers.CandidateBuffer.smt_code', index=12, + number=13, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='eye_label', full_name='org.mitre.iwp.buffers.CandidateBuffer.eye_label', index=13, + number=14, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='image_buffer', full_name='org.mitre.iwp.buffers.CandidateBuffer.image_buffer', index=14, + number=999, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='image_path', full_name='org.mitre.iwp.buffers.CandidateBuffer.image_path', index=15, + number=1999, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=313, + serialized_end=788, +) + + +_IMAGEBUFFER = _descriptor.Descriptor( + name='ImageBuffer', + full_name='org.mitre.iwp.buffers.ImageBuffer', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='data', full_name='org.mitre.iwp.buffers.ImageBuffer.data', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=790, + serialized_end=817, +) + + +_IMAGESERVICEQUERY = _descriptor.Descriptor( + name='ImageServiceQuery', + full_name='org.mitre.iwp.buffers.ImageServiceQuery', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='org.mitre.iwp.buffers.ImageServiceQuery.id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='path', full_name='org.mitre.iwp.buffers.ImageServiceQuery.path', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='imageBytes', full_name='org.mitre.iwp.buffers.ImageServiceQuery.imageBytes', index=2, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='imageType', full_name='org.mitre.iwp.buffers.ImageServiceQuery.imageType', index=3, + number=4, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='width', full_name='org.mitre.iwp.buffers.ImageServiceQuery.width', index=4, + number=5, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='height', full_name='org.mitre.iwp.buffers.ImageServiceQuery.height', index=5, + number=6, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _IMAGESERVICEQUERY_IMAGETYPE, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=820, + serialized_end=1077, +) + + +_TSHEPIICOMPAREREQUEST = _descriptor.Descriptor( + name='TShepiiCompareRequest', + full_name='org.mitre.iwp.buffers.TShepiiCompareRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='response1', full_name='org.mitre.iwp.buffers.TShepiiCompareRequest.response1', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='response2', full_name='org.mitre.iwp.buffers.TShepiiCompareRequest.response2', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1080, + serialized_end=1221, +) + + +_SERVICEREPLY = _descriptor.Descriptor( + name='ServiceReply', + full_name='org.mitre.iwp.buffers.ServiceReply', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='org.mitre.iwp.buffers.ServiceReply.id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='aciiResponse', full_name='org.mitre.iwp.buffers.ServiceReply.aciiResponse', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='biqtResponse', full_name='org.mitre.iwp.buffers.ServiceReply.biqtResponse', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='serviceResponse', full_name='org.mitre.iwp.buffers.ServiceReply.serviceResponse', index=3, + number=4, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='imageId', full_name='org.mitre.iwp.buffers.ServiceReply.imageId', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='imagePath', full_name='org.mitre.iwp.buffers.ServiceReply.imagePath', index=5, + number=6, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='contrastResponse', full_name='org.mitre.iwp.buffers.ServiceReply.contrastResponse', index=6, + number=7, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='cryptResponse', full_name='org.mitre.iwp.buffers.ServiceReply.cryptResponse', index=7, + number=8, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='cryptResponse2', full_name='org.mitre.iwp.buffers.ServiceReply.cryptResponse2', index=8, + number=9, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='dmipaResponse', full_name='org.mitre.iwp.buffers.ServiceReply.dmipaResponse', index=9, + number=10, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='contactclassifierResponse', full_name='org.mitre.iwp.buffers.ServiceReply.contactclassifierResponse', index=10, + number=11, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1224, + serialized_end=1813, +) + + +_ACIIRESPONSE = _descriptor.Descriptor( + name='AciiResponse', + full_name='org.mitre.iwp.buffers.AciiResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='horizontal', full_name='org.mitre.iwp.buffers.AciiResponse.horizontal', index=0, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='vertical', full_name='org.mitre.iwp.buffers.AciiResponse.vertical', index=1, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1816, + serialized_end=1954, +) + + +_BIQTRESPONSE = _descriptor.Descriptor( + name='BiqtResponse', + full_name='org.mitre.iwp.buffers.BiqtResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='contrast', full_name='org.mitre.iwp.buffers.BiqtResponse.contrast', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='irisDiameter', full_name='org.mitre.iwp.buffers.BiqtResponse.irisDiameter', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='percentVisibleIris', full_name='org.mitre.iwp.buffers.BiqtResponse.percentVisibleIris', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='quality', full_name='org.mitre.iwp.buffers.BiqtResponse.quality', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='imageHeight', full_name='org.mitre.iwp.buffers.BiqtResponse.imageHeight', index=4, + number=5, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='imageWidth', full_name='org.mitre.iwp.buffers.BiqtResponse.imageWidth', index=5, + number=6, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='irisCenterX', full_name='org.mitre.iwp.buffers.BiqtResponse.irisCenterX', index=6, + number=7, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='irisCenterY', full_name='org.mitre.iwp.buffers.BiqtResponse.irisCenterY', index=7, + number=8, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1957, + serialized_end=2139, +) + + +_CONTRASTRESPONSE = _descriptor.Descriptor( + name='ContrastResponse', + full_name='org.mitre.iwp.buffers.ContrastResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='imageBytes', full_name='org.mitre.iwp.buffers.ContrastResponse.imageBytes', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2141, + serialized_end=2179, +) + + +_TSHEPIIRESPONSE = _descriptor.Descriptor( + name='TShepiiResponse', + full_name='org.mitre.iwp.buffers.TShepiiResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='keypoints', full_name='org.mitre.iwp.buffers.TShepiiResponse.keypoints', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='crypts', full_name='org.mitre.iwp.buffers.TShepiiResponse.crypts', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='matchIndex', full_name='org.mitre.iwp.buffers.TShepiiResponse.matchIndex', index=2, + number=7, type=5, cpp_type=1, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='cropIris', full_name='org.mitre.iwp.buffers.TShepiiResponse.cropIris', index=3, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='cropPupil', full_name='org.mitre.iwp.buffers.TShepiiResponse.cropPupil', index=4, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='originalIris', full_name='org.mitre.iwp.buffers.TShepiiResponse.originalIris', index=5, + number=5, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='originalPupil', full_name='org.mitre.iwp.buffers.TShepiiResponse.originalPupil', index=6, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='inputImageWidth', full_name='org.mitre.iwp.buffers.TShepiiResponse.inputImageWidth', index=7, + number=8, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='inputImageHeight', full_name='org.mitre.iwp.buffers.TShepiiResponse.inputImageHeight', index=8, + number=9, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2182, + serialized_end=2576, +) + + +_TCIRCLE = _descriptor.Descriptor( + name='TCircle', + full_name='org.mitre.iwp.buffers.TCircle', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='center', full_name='org.mitre.iwp.buffers.TCircle.center', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='radius', full_name='org.mitre.iwp.buffers.TCircle.radius', index=1, + number=2, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2578, + serialized_end=2650, +) + + +_TPOINT = _descriptor.Descriptor( + name='TPoint', + full_name='org.mitre.iwp.buffers.TPoint', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='x', full_name='org.mitre.iwp.buffers.TPoint.x', index=0, + number=1, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='y', full_name='org.mitre.iwp.buffers.TPoint.y', index=1, + number=2, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='size', full_name='org.mitre.iwp.buffers.TPoint.size', index=2, + number=3, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2652, + serialized_end=2696, +) + + +_CRYPT = _descriptor.Descriptor( + name='Crypt', + full_name='org.mitre.iwp.buffers.Crypt', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='points', full_name='org.mitre.iwp.buffers.Crypt.points', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2698, + serialized_end=2752, +) + + +_DMIPARESPONSE = _descriptor.Descriptor( + name='DmipaResponse', + full_name='org.mitre.iwp.buffers.DmipaResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='filename', full_name='org.mitre.iwp.buffers.DmipaResponse.filename', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='irisDetected', full_name='org.mitre.iwp.buffers.DmipaResponse.irisDetected', index=1, + number=2, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='response', full_name='org.mitre.iwp.buffers.DmipaResponse.response', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='message', full_name='org.mitre.iwp.buffers.DmipaResponse.message', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='tmpPath', full_name='org.mitre.iwp.buffers.DmipaResponse.tmpPath', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='detectionCode', full_name='org.mitre.iwp.buffers.DmipaResponse.detectionCode', index=5, + number=6, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='processTimeMS', full_name='org.mitre.iwp.buffers.DmipaResponse.processTimeMS', index=6, + number=7, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2755, + serialized_end=2908, +) + + +_CONTACTCLASSIFIERRESPONSE = _descriptor.Descriptor( + name='ContactClassifierResponse', + full_name='org.mitre.iwp.buffers.ContactClassifierResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='response', full_name='org.mitre.iwp.buffers.ContactClassifierResponse.response', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='message', full_name='org.mitre.iwp.buffers.ContactClassifierResponse.message', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='tmpPath', full_name='org.mitre.iwp.buffers.ContactClassifierResponse.tmpPath', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='detectionCode', full_name='org.mitre.iwp.buffers.ContactClassifierResponse.detectionCode', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='processTimeMS', full_name='org.mitre.iwp.buffers.ContactClassifierResponse.processTimeMS', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2910, + serialized_end=3035, +) + +_IRISIMAGEBUFFER.fields_by_name['image'].message_type = _IMAGEBUFFER +_CANDIDATELISTBUFFER.fields_by_name['candidates'].message_type = _CANDIDATEBUFFER +_CANDIDATEBUFFER.fields_by_name['image_buffer'].message_type = _IMAGEBUFFER +_IMAGESERVICEQUERY.fields_by_name['imageType'].enum_type = _IMAGESERVICEQUERY_IMAGETYPE +_IMAGESERVICEQUERY_IMAGETYPE.containing_type = _IMAGESERVICEQUERY +_TSHEPIICOMPAREREQUEST.fields_by_name['response1'].message_type = _TSHEPIIRESPONSE +_TSHEPIICOMPAREREQUEST.fields_by_name['response2'].message_type = _TSHEPIIRESPONSE +_SERVICEREPLY.fields_by_name['aciiResponse'].message_type = _ACIIRESPONSE +_SERVICEREPLY.fields_by_name['biqtResponse'].message_type = _BIQTRESPONSE +_SERVICEREPLY.fields_by_name['serviceResponse'].enum_type = _SERVICERESPONSETYPE +_SERVICEREPLY.fields_by_name['contrastResponse'].message_type = _CONTRASTRESPONSE +_SERVICEREPLY.fields_by_name['cryptResponse'].message_type = _TSHEPIIRESPONSE +_SERVICEREPLY.fields_by_name['cryptResponse2'].message_type = _TSHEPIIRESPONSE +_SERVICEREPLY.fields_by_name['dmipaResponse'].message_type = _DMIPARESPONSE +_SERVICEREPLY.fields_by_name['contactclassifierResponse'].message_type = _CONTACTCLASSIFIERRESPONSE +_ACIIRESPONSE.fields_by_name['horizontal'].enum_type = _HORIZONTALALIGNMENT +_ACIIRESPONSE.fields_by_name['vertical'].enum_type = _VERTICALALIGNMENT +_TSHEPIIRESPONSE.fields_by_name['keypoints'].message_type = _TPOINT +_TSHEPIIRESPONSE.fields_by_name['crypts'].message_type = _CRYPT +_TSHEPIIRESPONSE.fields_by_name['cropIris'].message_type = _TCIRCLE +_TSHEPIIRESPONSE.fields_by_name['cropPupil'].message_type = _TCIRCLE +_TSHEPIIRESPONSE.fields_by_name['originalIris'].message_type = _TCIRCLE +_TSHEPIIRESPONSE.fields_by_name['originalPupil'].message_type = _TCIRCLE +_TCIRCLE.fields_by_name['center'].message_type = _TPOINT +_CRYPT.fields_by_name['points'].message_type = _TPOINT +DESCRIPTOR.message_types_by_name['IrisImageBuffer'] = _IRISIMAGEBUFFER +DESCRIPTOR.message_types_by_name['CandidateListBuffer'] = _CANDIDATELISTBUFFER +DESCRIPTOR.message_types_by_name['CandidateBuffer'] = _CANDIDATEBUFFER +DESCRIPTOR.message_types_by_name['ImageBuffer'] = _IMAGEBUFFER +DESCRIPTOR.message_types_by_name['ImageServiceQuery'] = _IMAGESERVICEQUERY +DESCRIPTOR.message_types_by_name['TShepiiCompareRequest'] = _TSHEPIICOMPAREREQUEST +DESCRIPTOR.message_types_by_name['ServiceReply'] = _SERVICEREPLY +DESCRIPTOR.message_types_by_name['AciiResponse'] = _ACIIRESPONSE +DESCRIPTOR.message_types_by_name['BiqtResponse'] = _BIQTRESPONSE +DESCRIPTOR.message_types_by_name['ContrastResponse'] = _CONTRASTRESPONSE +DESCRIPTOR.message_types_by_name['TShepiiResponse'] = _TSHEPIIRESPONSE +DESCRIPTOR.message_types_by_name['TCircle'] = _TCIRCLE +DESCRIPTOR.message_types_by_name['TPoint'] = _TPOINT +DESCRIPTOR.message_types_by_name['Crypt'] = _CRYPT +DESCRIPTOR.message_types_by_name['DmipaResponse'] = _DMIPARESPONSE +DESCRIPTOR.message_types_by_name['ContactClassifierResponse'] = _CONTACTCLASSIFIERRESPONSE +DESCRIPTOR.enum_types_by_name['HorizontalAlignment'] = _HORIZONTALALIGNMENT +DESCRIPTOR.enum_types_by_name['VerticalAlignment'] = _VERTICALALIGNMENT +DESCRIPTOR.enum_types_by_name['ServiceResponseType'] = _SERVICERESPONSETYPE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +IrisImageBuffer = _reflection.GeneratedProtocolMessageType('IrisImageBuffer', (_message.Message,), { + 'DESCRIPTOR' : _IRISIMAGEBUFFER, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.IrisImageBuffer) + }) +_sym_db.RegisterMessage(IrisImageBuffer) + +CandidateListBuffer = _reflection.GeneratedProtocolMessageType('CandidateListBuffer', (_message.Message,), { + 'DESCRIPTOR' : _CANDIDATELISTBUFFER, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.CandidateListBuffer) + }) +_sym_db.RegisterMessage(CandidateListBuffer) + +CandidateBuffer = _reflection.GeneratedProtocolMessageType('CandidateBuffer', (_message.Message,), { + 'DESCRIPTOR' : _CANDIDATEBUFFER, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.CandidateBuffer) + }) +_sym_db.RegisterMessage(CandidateBuffer) + +ImageBuffer = _reflection.GeneratedProtocolMessageType('ImageBuffer', (_message.Message,), { + 'DESCRIPTOR' : _IMAGEBUFFER, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.ImageBuffer) + }) +_sym_db.RegisterMessage(ImageBuffer) + +ImageServiceQuery = _reflection.GeneratedProtocolMessageType('ImageServiceQuery', (_message.Message,), { + 'DESCRIPTOR' : _IMAGESERVICEQUERY, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.ImageServiceQuery) + }) +_sym_db.RegisterMessage(ImageServiceQuery) + +TShepiiCompareRequest = _reflection.GeneratedProtocolMessageType('TShepiiCompareRequest', (_message.Message,), { + 'DESCRIPTOR' : _TSHEPIICOMPAREREQUEST, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.TShepiiCompareRequest) + }) +_sym_db.RegisterMessage(TShepiiCompareRequest) + +ServiceReply = _reflection.GeneratedProtocolMessageType('ServiceReply', (_message.Message,), { + 'DESCRIPTOR' : _SERVICEREPLY, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.ServiceReply) + }) +_sym_db.RegisterMessage(ServiceReply) + +AciiResponse = _reflection.GeneratedProtocolMessageType('AciiResponse', (_message.Message,), { + 'DESCRIPTOR' : _ACIIRESPONSE, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.AciiResponse) + }) +_sym_db.RegisterMessage(AciiResponse) + +BiqtResponse = _reflection.GeneratedProtocolMessageType('BiqtResponse', (_message.Message,), { + 'DESCRIPTOR' : _BIQTRESPONSE, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.BiqtResponse) + }) +_sym_db.RegisterMessage(BiqtResponse) + +ContrastResponse = _reflection.GeneratedProtocolMessageType('ContrastResponse', (_message.Message,), { + 'DESCRIPTOR' : _CONTRASTRESPONSE, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.ContrastResponse) + }) +_sym_db.RegisterMessage(ContrastResponse) + +TShepiiResponse = _reflection.GeneratedProtocolMessageType('TShepiiResponse', (_message.Message,), { + 'DESCRIPTOR' : _TSHEPIIRESPONSE, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.TShepiiResponse) + }) +_sym_db.RegisterMessage(TShepiiResponse) + +TCircle = _reflection.GeneratedProtocolMessageType('TCircle', (_message.Message,), { + 'DESCRIPTOR' : _TCIRCLE, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.TCircle) + }) +_sym_db.RegisterMessage(TCircle) + +TPoint = _reflection.GeneratedProtocolMessageType('TPoint', (_message.Message,), { + 'DESCRIPTOR' : _TPOINT, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.TPoint) + }) +_sym_db.RegisterMessage(TPoint) + +Crypt = _reflection.GeneratedProtocolMessageType('Crypt', (_message.Message,), { + 'DESCRIPTOR' : _CRYPT, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.Crypt) + }) +_sym_db.RegisterMessage(Crypt) + +DmipaResponse = _reflection.GeneratedProtocolMessageType('DmipaResponse', (_message.Message,), { + 'DESCRIPTOR' : _DMIPARESPONSE, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.DmipaResponse) + }) +_sym_db.RegisterMessage(DmipaResponse) + +ContactClassifierResponse = _reflection.GeneratedProtocolMessageType('ContactClassifierResponse', (_message.Message,), { + 'DESCRIPTOR' : _CONTACTCLASSIFIERRESPONSE, + '__module__' : 'Messages_pb2' + # @@protoc_insertion_point(class_scope:org.mitre.iwp.buffers.ContactClassifierResponse) + }) +_sym_db.RegisterMessage(ContactClassifierResponse) + + +# @@protoc_insertion_point(module_scope) diff --git a/inference/core/src/config.py b/inference/core/src/config.py new file mode 100755 index 0000000..643f81d --- /dev/null +++ b/inference/core/src/config.py @@ -0,0 +1,51 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +import argparse + +class Options: + def __init__(self): + self.parser = argparse.ArgumentParser(description="Iris annotation options") + + # MAIN + self.parser.add_argument("--use_cuda", + action="store_true", + help="if flag is specified, run on GPU") + self.parser.add_argument("--model_dir_path", + type=str, + help="directory path of previously trained model(s)", + default="logs") + # default="models") + + # DATASET OPTIONS + self.parser.add_argument("--dataset_dir_path", + type=str, + help="directory path to prepend to dataset file") + self.parser.add_argument("--dataset_file", + type=str, + help="absolute path of dataset file") + + # OPTIMIZATION OPTIONS + self.parser.add_argument("--batch_size", + type=int, + help="number of images per batch", + default=1) + + # CLASS OPTIONS + self.parser.add_argument('--class_options', + type=str, + nargs=3, + action='append', + help="custom class options (for trained model)", + default=[]) + + def parse(self): + self.options = self.parser.parse_args() + return self.options diff --git a/inference/core/src/main.py b/inference/core/src/main.py new file mode 100755 index 0000000..3982fd4 --- /dev/null +++ b/inference/core/src/main.py @@ -0,0 +1,128 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +import sys +import torch +import segment.data.info as info +import IrisAnnotation_pb2 as IrisAnnotation +import logging +logging.basicConfig(format='%(asctime)-15s %(message)s', stream=sys.stdout, level=logging.INFO) + +from segment.dataset import * +from segment.newNetwork import UNet +from utility import getClassOptions, splitDependsOnKeys, convert2DTo3D, generateScleraOutline, generateIrisOutline, generatePupilOutline, generateGeneralOutline, savePolygonsOverMaskImage + +class SegmentAndDetect(): + def __init__(self, cfg): + logger = logging.getLogger('iris-annotation.core.main.SegmentAndDetect') + # Model + self.cfg = cfg + self.model = UNet(n_classes=info.num_labels) + if self.cfg.use_cuda: + self.model = self.model.cuda() + logger.info("loading model: '%s'" % (str(cfg.model_dir_path))) + self.model.load_checkpoint(cfg.model_dir_path, self.cfg.use_cuda) + logger.info("create_dataloader") + + self.loader = create_dataloader(self.cfg.dataset_dir_path, self.cfg.batch_size, self.cfg.dataset_file) + logger.info("evaluate") + self.model = self.model.eval() #needed for the ASPP to run a batch size of 1 + + def run(self): + logger = logging.getLogger('iris-annotation.core.main.run') + output_data = [] + + for i, (fnames, images) in enumerate(self.loader): + logger.info("main cuda '%s'" % (str(self.cfg.use_cuda))) + if self.cfg.use_cuda: + images = images.cuda() + + # Part 1 - Semantic Segmentation # + try: + # https://stackoverflow.com/questions/56557151/how-to-retrieve-with-and-height-of-an-image-tensor-returned-by-tf-image-decode-j + + # get width and height + width = images.shape[3] + height = images.shape[2] + + # display width and height + print("The height of the image is: ", height) + print("The width of the image is: ", width) + + # the segmentation code works on images larger than 640x480 but can be very + # memory intensive, which docker env with limited resources can not support + if width and height < 900: + probs = self.model(images) + labels = torch.argmax(probs, dim=1) + else: + errStr = "Image size error" + raise ValueError(errStr) + + except ValueError as e: + errStr=(" error was "+ str(type(e))+str(e)) + logger.error(errStr) + if 'extra_info' in dir(e): + # print(e.extra_info) + errStr = (e.extra_info) + logger.error(errStr) + for j in range(len(labels)): + image2D = labels[j].cpu().numpy() + image3D = convert2DTo3D(image2D) + + # Part 2 - Instance Segmentation # + logger.info("Instance Segmentation ") + for k in range(len(image3D)): + options = getClassOptions(self.cfg.class_options, k) + + if "segmentType" in options: + if options["segmentType"] == "sclera" and "segmentDependsOn" in options and len(splitDependsOnKeys(options["segmentDependsOn"])) == 2: + dependsOnKeys = splitDependsOnKeys(options["segmentDependsOn"]) + + print("-----Performing Sclera Extraction for Class " + str(k) + " (Depends On Iris [Class " + dependsOnKeys[0] + "], Pupil [Class " + dependsOnKeys[1] + "])-----") + polygon = generateScleraOutline(image3D[k], image3D[int(dependsOnKeys[0])], image3D[int(dependsOnKeys[1])]) + elif options["segmentType"] == "iris" and "segmentDependsOn" in options and len(splitDependsOnKeys(options["segmentDependsOn"])) == 1: + dependsOnKeys = splitDependsOnKeys(options["segmentDependsOn"]) + + print("-----Performing Iris Extraction for Class " + str(k) + " (Depends On Pupil [Class " + dependsOnKeys[0] + "])-----") + polygon = generateIrisOutline(image3D[k], image3D[int(dependsOnKeys[0])]) + elif options["segmentType"] == "pupil": + print("-----Performing Pupil Extraction for Class " + str(k) + "-----") + polygon = generatePupilOutline(image3D[k]) + ## + elif options["segmentType"] == "eyelash": + print("-----Performing eyelash Extraction for Class " + str(k) + "-----") + polygon = generateGeneralOutline(image3D[k]) + elif options["segmentType"] == "eyebrow": + print("-----Performing eyebrow Extraction for Class " + str(k) + "-----") + polygon = generateGeneralOutline(image3D[k]) + ## + elif options["segmentType"] == "highlight": + print("-----Performing highlight Extraction for Class " + str(k) + "-----") + polygon = generateGeneralOutline(image3D[k]) + ## + elif options["segmentType"] == "border": + print("-----Performing border Extraction for Class " + str(k) + "-----") + polygon = generateGeneralOutline(image3D[k]) + ## + else: + print("-----Performing General Extraction for Class " + str(k) + "-----") + polygon = generateGeneralOutline(image3D[k]) + else: + print("-----Performing General Extraction for Class " + str(k) + "-----") + polygon = generateGeneralOutline(image3D[k]) + + annotation = IrisAnnotation.Annotation() + annotation.id = str(k) + annotation.colorHex = info.color_hex_map[k] + annotation.polygons.append(polygon) + + output_data.append(annotation) + + return output_data \ No newline at end of file diff --git a/inference/core/src/models/6248.pth b/inference/core/src/models/6248.pth new file mode 100644 index 0000000..aa478b6 Binary files /dev/null and b/inference/core/src/models/6248.pth differ diff --git a/inference/core/src/mq-main.py b/inference/core/src/mq-main.py new file mode 100755 index 0000000..792be47 --- /dev/null +++ b/inference/core/src/mq-main.py @@ -0,0 +1,501 @@ +#!/usr/bin/env python3 + +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +import os +import time +import warnings +import stomp +import sys +import logging +import time +import tempfile +from Messages_pb2 import ImageServiceQuery as Query +from IrisAnnotation_pb2 import IrisAnnotationReply + +from sys import exit +from config import Options +from main import SegmentAndDetect + +warnings.simplefilter(action='ignore', category=FutureWarning) +warnings.filterwarnings("ignore", category=RuntimeWarning) + +logging.basicConfig(format='%(asctime)-15s %(message)s', stream=sys.stdout, level=logging.INFO) +logging.getLogger("stomp").setLevel(logging.WARNING) +logging.getLogger("stomp.py").setLevel(logging.WARNING) + + +########################################################################################### + +ANNOTATION_REQUEST_QUEUE = "iris.annotation.request" +ANNOTATION_RESPONSE_QUEUE = "iris.annotation.response" +ARTEMIS_ANYCAST_HEADERS = {"destination-type": "ANYCAST"} +ARTEMIS_ANYCAST_SUBSCRIBE_HEADERS = {"subscription-type": "ANYCAST"} + +def stompHost(): + host_and_port = os.getenv('STOMP_HOST', "localhost:61613").split(':') + host = host_and_port[0] if host_and_port[0] else 'localhost' + port = host_and_port[1] if len(host_and_port) >= 2 else '61613' + return (host, port) + +def normalizeQueueName(queue): + if queue and queue.startswith('/queue/'): + return queue[len('/queue/'):] + return queue + +def username(): + username = '' + if 'STOMP_USER' in os.environ: + username = os.environ['STOMP_USER'] + return username + +def password(): + password = '' + if 'STOMP_PASSWORD' in os.environ: + password = os.environ['STOMP_PASSWORD'] + return password + +def stompHeartbeats(): + heartbeats = os.getenv('STOMP_HEARTBEATS', '10000,0').split(',') + try: + return (int(heartbeats[0]), int(heartbeats[1])) + except (IndexError, ValueError): + return (10000, 0) + +def modelDirectory(): + return "/usr/share/models" + +def tmpDirectory(): + return "/mnt/host/tmp" + +########################################################################################### + +def getFilesAndConfigs(cfg, query, reply, headers, conn): + logger = logging.getLogger('iris-annotation.core.mq_main.getFilesAndConfigs') + + in_file_list, cfg.dataset_file = createTmpFiles(query, reply, headers, conn) + if cfg.dataset_file is not None: + # cfg.use_cuda should be False on CPU images and True on GPU images. + # USE_CUDA should be set in the respective Dockerfile but will default to False (CPU only) if not set + cfg.use_cuda = True if os.environ.get('USE_CUDA', 'False').lower() == 'true' else False + + logger.info("cfg.use_cuda '%s'" % (str(cfg.use_cuda))) + cfg.dataset_dir_path = tmpDirectory() + logger.info("cfg.dataset_dir_path '%s'" % (str(cfg.dataset_dir_path))) + cfg.model_dir_path = modelDirectory() + logger.info("cfg.model_dir_path '%s'" % (str(cfg.model_dir_path))) + + classOptions = [] + #pupil = 4 + #eyebrow =5 + #iris =1 + #eyelash=2 + #scelara=3 + classOptions.append([str(1), "segmentType", "iris"]) + classOptions.append([str(1), "segmentDependsOn", "4"]) + classOptions.append([str(4), "segmentType", "pupil"]) + classOptions.append([str(3), "segmentType", "sclera"]) + classOptions.append([str(3), "segmentDependsOn", "1-4"]) + # + classOptions.append([str(2), "segmentType", "eyelash"]) + classOptions.append([str(5), "segmentType", "eyebrow"]) + + logger.info("Class options mq-main'%s'" % (str(classOptions))) + if len(classOptions) > 0: + cfg.class_options = classOptions + + return in_file_list, cfg + else: + return None, None + +def createTmpFiles(query, reply, headers, conn): + logger = logging.getLogger('iris-annotation.core.mq_main.createTmpFiles') + logger.info("createTmpFiles") + + in_file_list = [] + image_paths = [None] + + in_text_fd, in_text_file, errStr = createTmpFile(None) + if errStr is not None: + reply = formatResponse(reply, None, "ERROR", errStr, -1, None) + logger.error(errStr) + + response_queue = normalizeQueueName(ANNOTATION_RESPONSE_QUEUE) + send(reply, response_queue + "-failures") + acknowledge(query, headers, conn) + return None, None + dataset_file = in_text_file + in_file_list.append(in_text_file) + + try: + with os.fdopen(in_text_fd, 'w') as data_file: + for image_path in image_paths: + in_fd, in_file, errStr = createTmpFile(tmpDirectory()) + if errStr is not None: + reply = formatResponse(reply, None, "ERROR", errStr, -1, None) + logger.error(errStr) + + closeTmpFile(in_text_fd) + removeTmpFiles(in_file_list) + response_queue = normalizeQueueName(ANNOTATION_RESPONSE_QUEUE) + send(reply, response_queue + "-failures") + acknowledge(query, headers, conn) + return None, None + in_file_list.append(in_file) + try: + if image_path is not None: + if os.path.isfile(image_path) and os.access(image_path, os.R_OK): + # get the image from the Docker bind-mount based path + with os.fdopen(in_fd, 'wb') as image_file: + with open(image_path, 'rb') as host_image: + image_file.write(host_image.read()) + else: + errStr = "Data (Preparation) Error: The original file does not exist or is not readable!" + reply = formatResponse(reply, None, "ERROR", errStr, -1, None) + logger.error(errStr) + + closeTmpFile(in_fd) + closeTmpFile(in_text_fd) + removeTmpFiles(in_file_list) + response_queue = normalizeQueueName(ANNOTATION_RESPONSE_QUEUE) + send(reply, response_queue + "-failures") + acknowledge(query, headers, conn) + return None, None + else: + # get the image from the raw message + with os.fdopen(in_fd, 'wb') as image_file: + logger.info("Getting Image file from query.imageBytes") + image_file.write(query.imageBytes) + except Exception as e: + errStr = "Data (Preparation) Error: " + str(e) + reply = formatResponse(reply, None, "ERROR", errStr, -1, None) + logger.error(errStr) + + closeTmpFile(in_fd) + closeTmpFile(in_text_fd) + removeTmpFiles(in_file_list) + response_queue = normalizeQueueName(ANNOTATION_RESPONSE_QUEUE) + send(reply, response_queue + "-failures") + acknowledge(query, headers, conn) + return None, None + + data_file.write(os.path.basename(in_file) + ",None\n") + closeTmpFile(in_fd) + except Exception as e: + errStr = "Data (Preparation) Error: " + str(e) + reply = formatResponse(reply, None, "ERROR", errStr, -1, None) + logger.error(errStr) + + closeTmpFile(in_fd) + closeTmpFile(in_text_fd) + removeTmpFiles(in_file_list) + response_queue = normalizeQueueName(ANNOTATION_RESPONSE_QUEUE) + send(reply, response_queue + "-failures") + acknowledge(query, headers, conn) + return None, None + + closeTmpFile(in_text_fd) + return in_file_list, dataset_file + +def createTmpFile(tmpDirectory): + logger = logging.getLogger('iris-annotation.core.mq_main.createTmpFile') + errStr = None + logger.info("createTmpFile: '%s'" % (str(tmpDirectory))) + tmpArr = [] + try: + if tmpDirectory is not None: + tmpArr = tempfile.mkstemp(dir=tmpDirectory) + else: + tmpArr = tempfile.mkstemp() + + if len(tmpArr) != 2: + errStr = "Data (Pre-Preparation) Error: 'mkstemp()' returned a response with an unrecognized format!" + return None, None, errStr + except Exception as e: + errStr = "Data (Pre-Preparation) Error: " + str(e) + return None, None, errStr + + return tmpArr[0], tmpArr[1], errStr + +def closeTmpFile(fd): + try: + os.close(fd) + except OSError: + pass + +def removeTmpFiles(fname_list): + for fname in fname_list: + if os.path.exists(fname): + os.remove(fname) + +########################################################################################### + +class StompListenerProto(stomp.ConnectionListener): + conn = None + queueReceive = None + + def __init__(self, conn, queueReceive): + self.conn = conn + self.queueReceive = queueReceive + + stomp.ConnectionListener.__init__(self) + + def on_error(self, frame): + headers = frame.headers + message = frame.body + + logger = logging.getLogger('iris-annotation.core.mq_main.StompListenerProto.on_error') + + logger.error("Received an error '%s'" % (str(message))) + + query = Query() + query.ParseFromString(message) + + reply = IrisAnnotationReply() + if len(query.id): reply.id = query.id + + errStr = "Stomp Error: An unknown error occurred!" + reply = formatResponse(reply, None, "ERROR", errStr, -1, None) + logger.error(errStr) + + response_queue = normalizeQueueName(ANNOTATION_RESPONSE_QUEUE) + send(reply, response_queue + "-failures") + acknowledge(query, headers, self.conn) + + def on_message(self, frame): + logger = logging.getLogger('iris-annotation.core.mq_main.StompListenerProto.on_message') + headers = frame.headers + message = frame.body + + # logger.info("Byte Message Recieved: '%s'" % (str(message))) + logger.info("Byte Message Recieved: ") + # logger.info("header Info: '%s'" % (str(headers))) + + try: + + query = Query() + + + logger.info("Query Message Recieved b4: '%s'" % (str(query))) + + query.ParseFromString(message) + + if query.id: + logger.info("Query id: '%s'" % (str(query.id))) + if query.path: + logger.info("Query path: '%s'" % (str(query.path))) + if query.imageBytes: + logger.info("Query imageBytes: ") + if query.width: + logger.info("Query width: '%s'" % (str(query.width))) + + + except Exception as e: + errStr = "Error with Request Message : " + str(e) + logger.error(errStr) + + reply = IrisAnnotationReply() + + if len(query.id): reply.id = query.id + + cfgDefault = Options().parse() + logger.info("cfgDefault: '%s'" % (str(cfgDefault))) + files_list, cfg = getFilesAndConfigs(cfgDefault, query, reply, headers, self.conn) + if cfg is None: + return + + try: + logger.info("Running main iris annotation program mq-main...") + t0 = int(round(time.time() * 1000)) + + segAndDet = SegmentAndDetect(cfg) + logger.info("segDet complete") + results = segAndDet.run() + + t1 = int(round(time.time() * 1000)) + logger.info("Main iris annotation program complete mq-main") + + reply = formatResponse(reply, results, "OK", None, 1, t1-t0) + + removeTmpFiles(files_list) + response_queue = normalizeQueueName(ANNOTATION_RESPONSE_QUEUE) + send(reply, response_queue) + acknowledge(query, headers, self.conn) + except Exception as e: + errStr = "Error (within main iris annotation program): " + str(e) + reply = formatResponse(reply, None, "ERROR", errStr, -1, None) + logger.error(errStr) + + removeTmpFiles(files_list) + response_queue = normalizeQueueName(ANNOTATION_RESPONSE_QUEUE) + send(reply, response_queue + "-failures") + acknowledge(query, headers, self.conn) + return + + def on_disconnected(self): + logger = logging.getLogger('iris-annotation.core.mq_main.StompListenerProto.on_disconnected') + + logger.info("Connection to stomp host (request) %s was lost, attempting to reconnect." % (":".join(stompHost()))) + time.sleep(1) + pullConnection(self.conn, self.queueReceive) + +class StompReceiptProto(stomp.ConnectionListener): + conn = None + + def __init__(self, conn): + self.conn = conn + stomp.ConnectionListener.__init__(self) + + def on_error(self, frame): + headers = frame.headers + message = frame.body + + logger = logging.getLogger('iris-annotation.core.mq_main.StompReceiptProto.on_error') + + logger.info("Received error regarding stomp host (reply) %s\n%s\nError: %s" % (":".join(stompHost()), headers, message)) + + def on_receipt(self, frame): + headers = frame.headers + message = frame.body + + logger = logging.getLogger('iris-annotation.core.mq_main.StompReceiptProto.on_receipt') + + logger.info("Received receipt from stomp host (reply) %s\n%s" % (":".join(stompHost()), headers)) + + def on_disconnected(self): + logger = logging.getLogger('iris-annotation.core.mq_main.StompReceiptProto.on_disconnected') + + logger.info("Connection to stomp host (reply) %s was lost, attempting to reconnect." % (":".join(stompHost()))) + time.sleep(1) + pushConnection(self.conn) + +class StompDisconnectProto(stomp.ConnectionListener): + + def __init__(self): + stomp.ConnectionListener.__init__(self) + + def on_error(self, frame): + headers = frame.headers + message = frame.body + + logger = logging.getLogger('iris-annotation.core.mq_main.StompDisconnectProto.on_error') + + logger.info("Received error while disconnecting from stomp host %s\n%s\nError: %s" % (":".join(stompHost()), headers, message)) + + def on_receipt(self, frame): + headers = frame.headers + message = frame.body + + logger = logging.getLogger('iris-annotation.core.mq_main.StompDisconnectProto.on_receipt') + + logger.info("Received receipt for disconnecting from stomp host %s\n%s" % (":".join(stompHost()), headers)) + +########################################################################################### + +def pushConnection(conn): + conn.connect(username(), password(), wait=True) + +def pullConnection(conn, queueReceive): + logger = logging.getLogger('iris-annotation.core.mq_main.pullConnection') + + conn.connect(username(), password(), wait=True) + logger.info("Subscribing to %s" % (str(queueReceive))) + conn.subscribe( + destination=queueReceive, + id=1, + ack='client-individual', + headers={**ARTEMIS_ANYCAST_SUBSCRIBE_HEADERS, 'activemq.prefetchSize': '1'}) + +def formatResponse(obj, results, response, message, code, time): + if results is not None: obj.results.extend(results) + if response is not None: obj.response = str(response) + if message is not None: obj.responseMessage = str(message) + if code is not None: obj.responseCode = str(code) + if time is not None: obj.responseTimeMS = str(time) + + return obj + +def send(query, queue): + logger = logging.getLogger('iris-annotation.core.mq_main.send') + + receiptStr = query.id + '_reply-receipt' + wReceipt = stomp.WaitingListener(receiptStr) + + conn = stomp.Connection([stompHost()], heartbeats=stompHeartbeats()) + conn.set_listener('receiptProto', StompReceiptProto(conn)) + conn.set_listener('wReceiptListener', wReceipt) + pushConnection(conn) + + logger.info("Sending reply to %s" % (str(queue))) + conn.send( + body=query.SerializeToString(), + destination=queue, + headers={**ARTEMIS_ANYCAST_HEADERS, 'persistent': 'true', 'receipt': receiptStr}) + wReceipt.wait_on_receipt() + conn.remove_listener('receiptProto') + conn.remove_listener('wReceiptListener') + conn.set_listener('', StompDisconnectProto()) + + logger.info("Disconnecting from stomp host (reply) %s" % (":".join(stompHost()))) + disconnectStr = query.id + "_reply-disconnect" + wDisconnect = stomp.WaitingListener(disconnectStr) + conn.set_listener('wDisconnectListener', wDisconnect) + + conn.disconnect(receipt=disconnectStr) + wDisconnect.wait_on_disconnected() + +def acknowledge(query, headers, conn): + if 'message-id' in headers and 'subscription' in headers: + conn.ack(headers['message-id'], headers['subscription']) + +def destroyPullConnection(conn): + logger = logging.getLogger('iris-annotation.core.mq_main.destroyPullConnection') + + conn.remove_listener('listenerProto') + conn.set_listener('', StompDisconnectProto()) + + logger.info("Disconnecting from stomp host (receive) %s" % (":".join(stompHost()))) + disconnectStr = "stomp_receive-disconnect" + wDisconnect = stomp.WaitingListener(disconnectStr) + conn.set_listener('wDisconnectListener', wDisconnect) + + conn.disconnect(receipt=disconnectStr) + wDisconnect.wait_on_disconnected() + +########################################################################################### + +def main(): + logger = logging.getLogger('iris-annotation.core.mq_main.main') + + request_queue = normalizeQueueName(ANNOTATION_REQUEST_QUEUE) + logger.info("Request queue: %s" % (str(request_queue))) + if not request_queue: + logger.error("The protobuf request queue was not specified, exiting.!") + exit(-1) + + logger.info("Connecting to stomp host (receive) " + ":".join(stompHost())) + conn = stomp.Connection([stompHost()], auto_decode=False, heartbeats=stompHeartbeats()) + conn.set_listener('listenerProto', StompListenerProto(conn, request_queue)) + pullConnection(conn, request_queue) + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + pass + finally: + if request_queue: + destroyPullConnection(conn) + +########################################################################################### + +if __name__ == '__main__': + main() diff --git a/inference/core/src/segment/__init__.py b/inference/core/src/segment/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/inference/core/src/segment/aspp.py b/inference/core/src/segment/aspp.py new file mode 100644 index 0000000..45e35be --- /dev/null +++ b/inference/core/src/segment/aspp.py @@ -0,0 +1,71 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +import torch.nn as nn +import torch + + +# https://github.com/chenxi116/DeepLabv3.pytorch/blob/master/deeplab.py +class ASPP(nn.Module): + + def __init__(self, C, depth, num_classes=6, conv=nn.Conv2d, norm=nn.BatchNorm2d, momentum=0.0003, mult=1): + super(ASPP, self).__init__() + self._C = C + self._depth = depth + self._num_classes = num_classes + + self.global_pooling = nn.AdaptiveAvgPool2d(1) + self.relu = nn.ReLU(inplace=True) + self.aspp1 = conv(C, depth, kernel_size=1, stride=1, bias=False) + self.aspp2 = conv(C, depth, kernel_size=3, stride=1, + dilation=int(6*mult), padding=int(6*mult), + bias=False) + self.aspp3 = conv(C, depth, kernel_size=3, stride=1, + dilation=int(12*mult), padding=int(12*mult), + bias=False) + self.aspp4 = conv(C, depth, kernel_size=3, stride=1, + dilation=int(18*mult), padding=int(18*mult), + bias=False) + self.aspp5 = conv(C, depth, kernel_size=1, stride=1, bias=False) + self.aspp1_bn = norm(depth, momentum) + self.aspp2_bn = norm(depth, momentum) + self.aspp3_bn = norm(depth, momentum) + self.aspp4_bn = norm(depth, momentum) + self.aspp5_bn = norm(depth, momentum) + self.conv2 = conv(depth * 5, depth, kernel_size=1, stride=1, + bias=False) + self.bn2 = norm(depth, momentum) + self.conv3 = nn.Conv2d(depth, depth, kernel_size=1, stride=1) # TODO + + def forward(self, x): + x1 = self.aspp1(x) + x1 = self.aspp1_bn(x1) + x1 = self.relu(x1) + x2 = self.aspp2(x) + x2 = self.aspp2_bn(x2) + x2 = self.relu(x2) + x3 = self.aspp3(x) + x3 = self.aspp3_bn(x3) + x3 = self.relu(x3) + x4 = self.aspp4(x) + x4 = self.aspp4_bn(x4) + x4 = self.relu(x4) + x5 = self.global_pooling(x) + x5 = self.aspp5(x5) + x5 = self.aspp5_bn(x5) + x5 = self.relu(x5) + x5 = nn.Upsample((x.shape[2], x.shape[3]), mode='bilinear', + align_corners=True)(x5) + x = torch.cat((x1, x2, x3, x4, x5), 1) + x = self.conv2(x) + x = self.bn2(x) + x = self.relu(x) + x = self.conv3(x) + return x \ No newline at end of file diff --git a/inference/core/src/segment/data/__init__.py b/inference/core/src/segment/data/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/inference/core/src/segment/data/info.py b/inference/core/src/segment/data/info.py new file mode 100755 index 0000000..d55e5a8 --- /dev/null +++ b/inference/core/src/segment/data/info.py @@ -0,0 +1,68 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +""" +Segmentation dataset information. +""" + + +# Integer label to RGB Color code +label_to_color_map = { + 0: (0,0,0), # Background + # 1: (255,255,255), # Pupil + 1: (255,0,0), # Pupil + # 2: (0,0,255), # Iris + 2: (255,255,255), # Iris + 3: (0,255,0), # Sclera + # 4: (255,0,0), # Eyelash + 4: (0,0,255), # Eyelash + 5: (0,255,255), # Eyebrow + 6: (255,255,0), # Highlight #6248 and below + 7: (255,0,255), # Border + 8: (51,255,255), # Crypt + 9: (255,153,51), # Ridge + 10: (0,165,255) # Contact +} + + + +# RGB color code to integer label map +color_to_label_map = {} +for (k,v) in label_to_color_map.items(): + color_to_label_map[v] = k + +# RGB Color (HEX) code +color_hex_map = { + 0: "#000000", + # 1: "#FFFFFF", + 1: "#FF0000", + # 2: "#0000FF", + 2: "#FFFFFF", + 3: "#00FF00", + # 4: "#FF0000", + 4: "#0000FF", + 5: "#00FFFF", + 6: "#FFFF00", #6248 and below + 7: "#FF00FF", + 8: "#33FFFF", + 9: "#FF9933", + 10: "#00A5FF" +} + +# Gray is used by LG4000 sensor for regions out of bounds +background_color = 127 +background_label = 0 + +# Number of segmentation classes +num_labels = len(list(label_to_color_map.keys())) + +# Image dimensions +width = 640 +height = 480 \ No newline at end of file diff --git a/inference/core/src/segment/dataset.py b/inference/core/src/segment/dataset.py new file mode 100755 index 0000000..90b8277 --- /dev/null +++ b/inference/core/src/segment/dataset.py @@ -0,0 +1,218 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +import os +import numpy as np +from PIL import Image +from ntpath import basename + +import torch +import torchvision.transforms as tvt +import segment.data.info as info + + +def pil_loader(path, mode='L'): + """ + Utility function for loading PIL Images. + + Params: + path (str): path to image file + mode (str): 'L' to load as grayscale, 'RGB' to load as rgb image + """ + im = Image.open(path) + return im.convert(mode) + + +def convert_color_to_class(x): + """ + Utility function for converting 3D RGB segmentation array to a + 2D array of segmentation labels. + + Params: + x (PIL Image): RGB image of size (h,w,c). + Returns: + y (PIL Image): Image of size (h,w) with RGB values converted to + segmentation labels, according to data.info + """ + x = np.array(x) + y = np.zeros((x.shape[:2]), dtype=np.int) + + for k, v in info.color_to_label_map.items(): + y[(x == k).all(axis=2)] = v + + y = Image.fromarray(np.uint8(y)) + return y + + +def convert_class_to_color(x): + """ + Utility function for converting 2D array of segmentation labels to a + 3D RGB segmentation array. + + Params: + x (PIL Image): Image of size (h,w) of segmentation labels. + Returns: + y (PIL Image): Image of size (h,w,c) with RGB values + according to data.info + """ + h, w = x.shape + y = np.zeros((h,w,3)) + + for (i, j), k in np.ndenumerate(x): + y[i,j] = info.label_to_color_map[k] + return y + + +class EyeSegTest(torch.utils.data.Dataset): + def __init__(self, data_file, root_path, do_augment): + """ + Dataset object. + Performs data augmentation. + + For labeled images, `data_file` should be formatted as a text file with + one training pair per line. For example, + path_to_image_file,path_to_segmentation_map + path_to_image_file,path_to_segmentation_map + ... + + For an evaluation file (no labels), replace the label entry with 'None': + path_to_image_file,None + path_to_image_file,None + ... + + Params: + data_file (str): text file containing paths to training images. + root_path (str): base path prepended to paths from data_file + do_augment (bool): flag to control data augmentation steps. + should be true for training, false for testing. + """ + super(EyeSegTest, self).__init__() + + # Extract file paths from text file + with open(data_file, 'r') as f: + lines = f.readlines() + files = [x.strip().split(',') for x in lines] + + # Prepend dataset root + self.has_labels = False + self.files = [] + for (img_path, label_path) in files: + img_path = os.path.join(root_path, img_path) + if label_path != 'None': + label_path = os.path.join(root_path, label_path) + self.has_labels = True + self.files.append("%s,%s" % (img_path, label_path)) + self.files = np.array(self.files) + + # Loading objects + self.loader = pil_loader + self.to_tensor = tvt.ToTensor() + + # Transformations + self.do_augment = do_augment + self.color_jitter = tvt.ColorJitter(0.2, 0.2, 0.2, 0.2) + self.random_affine_parameters = lambda: tvt.RandomAffine.get_params( + degrees=(-30,30), + translate=[0.1,0.1], + shears=None, + img_size=(info.width,info.height), + scale_ranges=[0.9,1.1] + ) + self.h_flip = tvt.RandomHorizontalFlip(p=1.0) + self.v_flip = tvt.RandomVerticalFlip(p=1.0) + + + def augment(self, xs): + """ + Performs same data augmentation on list of images. + + Params: + xs (list[PIL Image]): list of images, either [img, label] or [img] + Returns: + ys (list[PIL Image]): list of augmented images + """ + ys = [] + + # Augmentation parameters + do_h_flip = np.random.random() > 0.5 + do_v_flip = np.random.random() > 0.5 + angle, trans, scale, _ = self.random_affine_parameters() + + for i, x in enumerate(xs): + # Apply color jitter + # Only apply color jitter to images, not labels + if i == 0: + x = self.color_jitter(x) + + # Flip images + x = self.h_flip(x) if do_h_flip else x + x = self.v_flip(x) if do_v_flip else x + + # Affine transformation + fillcolor = info.background_color if i == 0 else info.background_label + x = tvt.functional.affine(x, angle, trans, scale, shear=0, fillcolor=fillcolor) + + ys.append(x) + return ys + + + def __getitem__(self, index): + """ + Fetch one training pair from the dataset. + + Returns: + (image, label) pair if possible; + otherwise (image) + """ + # Load image + img_path, label_path = self.files[index].split(',') + img_id = basename(img_path).split('.')[0] + img = self.loader(img_path, mode='L') + + # Load label + if self.has_labels: + label = self.loader(label_path, mode='RGB') + label = convert_color_to_class(label) + + # Augment + if self.do_augment: + if self.has_labels: + img, label = self.augment([img, label]) + label = torch.from_numpy(np.int64(label)).squeeze() + else: + img = self.augment([img])[0] + + # Return + img = self.to_tensor(img) + if self.has_labels: + label = torch.from_numpy(np.int64(label)).squeeze() + return img, label + return img_id, img + + + def __len__(self): + return len(self.files) + + +def create_dataloader(root_path, batch_size, data_file, do_train=False): + """ + Create DataLoader. + + Params: + root_path (str): base path prepended to paths from data_file + batch_size (int): number of images per batch + data_file (str): text file containing paths to training images. + do_train (bool): flag specifying whether or not we're training + """ + dataset = EyeSegTest(data_file, root_path, do_train) + dataloader = torch.utils.data.DataLoader(dataset, + batch_size=batch_size, + shuffle=do_train, num_workers=2, drop_last=False) + return dataloader \ No newline at end of file diff --git a/inference/core/src/segment/newNetwork.py b/inference/core/src/segment/newNetwork.py new file mode 100644 index 0000000..f37d1bd --- /dev/null +++ b/inference/core/src/segment/newNetwork.py @@ -0,0 +1,187 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +import os +from glob import glob +from ntpath import basename + +import torch +from torch import nn +import torch.nn.functional as F + +from .aspp import ASPP + +class UNet(nn.Module): + def __init__( + self, + in_channels=1, + n_classes=2, + depth=5, + wf=4, + padding=True, + batch_norm=True, + ): + """ + Implementation of U-Net. + https://discuss.pytorch.org/t/unet-implementation/426 + + Args: + in_channels (int): number of input channels + n_classes (int): number of output channels + depth (int): depth of the network + wf (int): number of filters in the first layer is 2**wf + padding (bool): if True, apply padding such that the input shape + is the same as the output. + This may introduce artifacts + batch_norm (bool): Use BatchNorm after layers with an + activation function + """ + super(UNet, self).__init__() + self.padding = padding + self.depth = depth + prev_channels = in_channels + + # Down path + self.down_path = nn.ModuleList() + for i in range(depth): + self.down_path.append( + UNetConvBlock(prev_channels, 2 ** (wf + i), padding, batch_norm) + ) + prev_channels = 2 ** (wf + i) + + # Up path + self.up_path = nn.ModuleList() + for i in reversed(range(depth - 1)): + self.up_path.append( + UNetUpBlock(prev_channels, 2 ** (wf + i), padding, batch_norm) + ) + prev_channels = 2 ** (wf + i) + + # Classification layer + self.last = nn.Conv2d(prev_channels, n_classes, kernel_size=1) + + # Embedding layers + self.embed_layer = nn.Conv2d(256, 1, 1) + + # ASPP + self.aspp = ASPP(256, 256) + + # @autocast() + def forward(self, x): + blocks = [] + for i, down in enumerate(self.down_path): + x = down(x) + if i != len(self.down_path) - 1: + blocks.append(x) + x = F.max_pool2d(x, 2) + emb = x # torch.Size([4, 256, 30, 40]) + + # Atrous convolutions + emb = self.aspp(emb) + + for i, up in enumerate(self.up_path): + x = up(x, blocks[-i - 1]) + output = self.last(x) + return output + + def embed(self, x): + blocks = [] + for i, down in enumerate(self.down_path): + x = down(x) + if i != len(self.down_path) - 1: + blocks.append(x) + x = F.max_pool2d(x, 2) + emb = x # torch.Size([n, 256, 14, 14]) + + # Atrous convolutions + # emb = self.aspp(emb) + + # Embedding + emb = self.embed_layer(emb) + emb = torch.flatten(emb, start_dim=1) + return emb + + + def load_checkpoint(self, ckpt_dir, use_cuda): + # Find most recent checkpoint + model_files = glob(os.path.join(ckpt_dir, '*.pth')) + print("using NEW network and model file:" + str(model_files)) + if len(model_files) == 0: + return 0 + model_epochs = [int(basename(f)[:-4]) for f in model_files if 'stage' not in f] + max_model_epoch = str(max(model_epochs)).zfill(3) + ckpt_file = [f for f in model_files if max_model_epoch in f][0] + + # Load checkpoint + if use_cuda: + try: + ckpt = torch.load(ckpt_file, map_location='cuda:0') + except: + ckpt = torch.load(ckpt_file, map_location='cuda:1') + else: + ckpt = torch.load(ckpt_file, map_location='cpu') + try: + self.load_state_dict(ckpt, strict=False) + except: + ckpt = {k: v for k, v in ckpt.items() if 'last' not in k} + self.load_state_dict(ckpt, strict=False) + print("Loaded %s. Resuming training from %s." % (ckpt_file, max_model_epoch)) + + # Return the resuming epoch number + return int(max_model_epoch) + + +class UNetConvBlock(nn.Module): + def __init__(self, in_size, out_size, padding, batch_norm): + super(UNetConvBlock, self).__init__() + block = [] + + block.append(nn.Conv2d(in_size, out_size, kernel_size=3, padding=int(padding))) + block.append(nn.ReLU()) + if batch_norm: + block.append(nn.BatchNorm2d(out_size)) + + block.append(nn.Conv2d(out_size, out_size, kernel_size=3, padding=int(padding))) + block.append(nn.ReLU()) + if batch_norm: + block.append(nn.BatchNorm2d(out_size)) + block.append(nn.Dropout(p=0.1)) + + self.block = nn.Sequential(*block) + + # @autocast() + def forward(self, x): + out = self.block(x) + return out + + +class UNetUpBlock(nn.Module): + def __init__(self, in_size, out_size, padding, batch_norm): + super(UNetUpBlock, self).__init__() + self.up = nn.ConvTranspose2d(in_size, out_size, kernel_size=2, stride=2) + self.conv_block = UNetConvBlock(in_size, out_size, padding, batch_norm) + + def center_crop(self, layer, target_size): + _, _, layer_height, layer_width = layer.size() + diff_y = (layer_height - target_size[0]) // 2 + diff_x = (layer_width - target_size[1]) // 2 + return layer[ + :, :, diff_y : (diff_y + target_size[0]), diff_x : (diff_x + target_size[1]) + ] + + # @autocast() + def forward(self, x, bridge): + up = self.up(x) + crop1 = self.center_crop(bridge, up.shape[2:]) + out = torch.cat([up, crop1], 1) + out = self.conv_block(out) + + return out + diff --git a/inference/core/src/utility.py b/inference/core/src/utility.py new file mode 100644 index 0000000..64e3cce --- /dev/null +++ b/inference/core/src/utility.py @@ -0,0 +1,117 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +import numpy +import cv2 +import IrisAnnotation_pb2 as IrisAnnotation +import segment.data.info as info + +def getClassOptions(classOptions, key): + options = {} + + classKeys = [] + classValues = [] + for classOption in classOptions: + if int(classOption[0]) == key: + classKeys.append(classOption[1]) + classValues.append(classOption[2]) + + counter = 0 + for classKey in classKeys: + options[classKey] = classValues[counter] + counter += 1 + + return options + +def splitDependsOnKeys(dependsOnKeys): + return dependsOnKeys.split('-') + +def convert2DTo3D(image2D): + if (image2D.ndim == 2): + #labels, rows, columns + image3D = numpy.zeros((info.num_labels, image2D.shape[0], image2D.shape[1])) + + label = 0 + while label < info.num_labels: + row = 0 + while row < image2D.shape[0]: + column = 0 + while column < image2D.shape[1]: + if (image2D[row,column] == label): + image3D[label,row,column] = 1 + column += 1 + row += 1 + label += 1 + + return image3D + else: + return None + +def generateScleraOutline(scleraMask, irisMask, pupilMask): + polygonSclera = generateGeneralOutline(scleraMask + irisMask + pupilMask) + polygonIris = generateGeneralOutline(irisMask + pupilMask) + polygonSclera.inline.append(polygonIris.outline) + + return polygonSclera + +def generateIrisOutline(irisMask, pupilMask): + polygonIris = generateGeneralOutline(irisMask + pupilMask) + polygonPupil = generateGeneralOutline(pupilMask) + polygonIris.inline.append(polygonPupil.outline) + + return polygonIris + +def generatePupilOutline(pupilMask): + polygonPupil = generateGeneralOutline(pupilMask) + + return polygonPupil + +def generateGeneralOutline(imageMask): + imageMask = (imageMask * 255.0).astype(numpy.uint8) + + contours, hierarchy = cv2.findContours(imageMask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + polygon = IrisAnnotation.Polygon() + if contours is not None and len(contours): + maxContour = max(contours, key = cv2.contourArea) + + for k in range(len(maxContour)): + point = IrisAnnotation.Point() + point.x = maxContour[k][0][0] + point.y = maxContour[k][0][1] + polygon.outline.points.append(point) + + return polygon + +def savePolygonsOverMaskImage(mask, polygon, path): + mask = (mask * 255.0).astype(numpy.uint8) + image = cv2.cvtColor(mask, cv2.COLOR_GRAY2RGB) + + outline = polygon.outline + for k in range(len(outline.points)): + image[outline.points[k].y,outline.points[k].x,1] = 0 + image[outline.points[k].y,outline.points[k].x,1] = 255 + image[outline.points[k].y,outline.points[k].x,2] = 0 + if (k + 1 == len(outline.points)): + cv2.line(image, (outline.points[k].x, outline.points[k].y), (outline.points[0].x, outline.points[0].y), (0, 255, 0), 1) + else: + cv2.line(image, (outline.points[k].x, outline.points[k].y), (outline.points[k+1].x, outline.points[k+1].y), (0, 255, 0), 1) + + inlineList = polygon.inline + for j in range(len(inlineList)): + for k in range(len(inlineList[j].points)): + image[inlineList[j].points[k].y,inlineList[j].points[k].x,1] = 0 + image[inlineList[j].points[k].y,inlineList[j].points[k].x,1] = 255 + image[inlineList[j].points[k].y,inlineList[j].points[k].x,2] = 0 + if (k + 1 == len(inlineList[j].points)): + cv2.line(image, (inlineList[j].points[k].x, inlineList[j].points[k].y), (inlineList[j].points[0].x, inlineList[j].points[0].y), (0, 255, 0), 1) + else: + cv2.line(image, (inlineList[j].points[k].x, inlineList[j].points[k].y), (inlineList[j].points[k+1].x, inlineList[j].points[k+1].y), (0, 255, 0), 1) + + cv2.imwrite(path, image) \ No newline at end of file diff --git a/iris-workstation/iwp-simple/docker-compose.local.yml b/iris-workstation/iwp-simple/docker-compose.local.yml deleted file mode 100644 index a54ae38..0000000 --- a/iris-workstation/iwp-simple/docker-compose.local.yml +++ /dev/null @@ -1,126 +0,0 @@ -# -# NOTICE -# -# This software (or technical data) was produced for the U. S. Government -# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV -# (May 2014) – Alternative IV (Dec 2007) -# -# (c) 2024 The MITRE Corporation. All Rights Reserved. -# - -#IMPORTANT! -#To deploy this compose file, set the following environment variables in a .env file. -#DATABASE_PASSWORD, DATABASE_USERNAME, DATABASE_ROOT_PASSWORD -#See the below docs link for details about .env files -#https://docs.docker.com/compose/environment-variables/set-environment-variables/#compose-file - -services: - activemq: - image: rmohr/activemq:5.15.9-alpine - container_name: activemq - restart: always - ports: - - 8161:8161 - - 61616:61616 - - 61613:61613 - # environment: - # - ACTIVEMQ_LOGGER_LOGLEVEL=DEBUG - - #This is commented out here so that you can run the iwp service locally (via the jar / yarn dev server) for testing and development. - # iwp: - # image: ghcr.io/mitre/iat/iwp-web:latest - # # restart: always - # depends_on: - # - "activemq" - # - "mysql" - # container_name: iwp - # ports: - # - 8080:8080 - # volumes: - # - "./iwp/config:/app/config" - # environment: - # - DATABASE_HOST=iwp_mysql - # # - DATABASE_HOST=localhost - # # - DATABASE_HOST=iwp-simple_mysql_1 - # - DATABASE_USER=${DATABASE_USERNAME} - # - DATABASE_PASSWORD=${DATABASE_PASSWORD} - # - DATABASE_NAME=iwp - # - DATABASE_PORT=3306 - # restart: always - # env_file: .env - - iwp-acii: - image: ghcr.io/mitre/iat/iwp-acii:latest - container_name: acii - restart: always - depends_on: - - "activemq" - - iwp-biqt: - image: ghcr.io/mitre/iat/iwp-biqt:latest - container_name: biqt - restart: always - depends_on: - - "activemq" - volumes: - - "./iwp-biqt/config:/app/config" - - iwp-annotation: - image: ghcr.io/mitre/iat/iwp-annotation:latest - container_name: annotation - restart: always - depends_on: - - "activemq" - environment: - - STOMP_HOST=activemq:61613 - - IRIS-ANNOTATION_PROTO_RECEIVE_QUEUE=iris.annotation.request - - IRIS-ANNOTATION_PROTO_REPLY_QUEUE=iris.annotation.response - - iwp-tshepii: - image: ghcr.io/mitre/iat/iwp-tshepii:latest - container_name: tshepii - restart: always - depends_on: - - "activemq" - volumes: - - "./iwp-tshepii/config:/etc/tshepii-config" - environment: - - LOG4CXX_CONFIGURATION=/etc/tshepii-config/log4cxx.conf - - TSHEPII_CONFIG_FILE=/etc/tshepii-config/tshepii.conf - - iwp-pdm: - image: ghcr.io/mitre/iat/iwp-pdm:latest - container_name: pdm - restart: always - depends_on: - - "activemq" - - mysql: - image: mysql:8 - container_name: iwp_mysql - restart: always - environment: - # MYSQL_DATABASE: 'iwp' - MYSQL_DATABASE: iwp - # So you don't have to use root, but you can if you like - MYSQL_USER: ${DATABASE_USERNAME} - # You can use whatever password you like - MYSQL_PASSWORD: ${DATABASE_PASSWORD} - # Password for root access - MYSQL_ROOT_PASSWORD: ${DATABASE_ROOT_PASSWORD} - MYSQL_ROOT_HOST: '%' - ports: - # : < MySQL Port running inside container> - - '3306:3306' - expose: - # Opens port 3306 on the container - - '3306' - # Where our data will be persisted - volumes: - - my-db:/var/lib/mysql - security_opt: - - seccomp:unconfined - env_file: .env - -volumes: - my-db: diff --git a/iris-workstation/iwp-simple/docker-compose.yml b/iris-workstation/iwp-simple/docker-compose.yml deleted file mode 100644 index f47c71e..0000000 --- a/iris-workstation/iwp-simple/docker-compose.yml +++ /dev/null @@ -1,132 +0,0 @@ -# -# NOTICE -# -# This software (or technical data) was produced for the U. S. Government -# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV -# (May 2014) – Alternative IV (Dec 2007) -# -# (c) 2024 The MITRE Corporation. All Rights Reserved. -# - -#IMPORTANT! -#To deploy this compose file, set the following environment variables in a .env file. -#DATABASE_PASSWORD, DATABASE_USERNAME, DATABASE_ROOT_PASSWORD -#See the below docs link for details about .env files -#https://docs.docker.com/compose/environment-variables/set-environment-variables/#compose-file - -services: - activemq: - image: rmohr/activemq:5.15.9-alpine - container_name: activemq - restart: always - ports: - - 8161:8161 - - 61616:61616 - - 61613:61613 - # environment: - # - ACTIVEMQ_LOGGER_LOGLEVEL=DEBUG - - iwp: - image: ghcr.io/mitre/iat/iwp-web:latest - restart: always - depends_on: - mysql: - condition: service_healthy - activemq: - condition: service_started - container_name: iwp - ports: - - 8080:8080 - volumes: - - "./iwp/config:/app/config" - environment: - - DATABASE_HOST=iwp_mysql - # - DATABASE_HOST=localhost - # - DATABASE_HOST=iwp-simple_mysql_1 - - DATABASE_USER=${DATABASE_USERNAME} - - DATABASE_PASSWORD=${DATABASE_PASSWORD} - - DATABASE_NAME=iwp - - DATABASE_PORT=3306 - env_file: .env - - iwp-acii: - image: ghcr.io/mitre/iat/iwp-acii:latest - container_name: acii - restart: always - depends_on: - - "activemq" - - iwp-biqt: - image: ghcr.io/mitre/iat/iwp-biqt:latest - container_name: biqt - restart: always - depends_on: - - "activemq" - volumes: - - "./iwp-biqt/config:/app/config" - platform: linux/amd64 - - iwp-annotation: - image: ghcr.io/mitre/iat/iwp-annotation:latest - container_name: annotation - restart: always - depends_on: - - "activemq" - environment: - - STOMP_HOST=activemq:61613 - - IRIS-ANNOTATION_PROTO_RECEIVE_QUEUE=iris.annotation.request - - IRIS-ANNOTATION_PROTO_REPLY_QUEUE=iris.annotation.response - - iwp-tshepii: - image: ghcr.io/mitre/iat/iwp-tshepii:latest - container_name: tshepii - restart: always - depends_on: - - "activemq" - volumes: - - "./iwp-tshepii/config:/etc/tshepii-config" - environment: - - LOG4CXX_CONFIGURATION=/etc/tshepii-config/log4cxx.conf - - TSHEPII_CONFIG_FILE=/etc/tshepii-config/tshepii.conf - - iwp-pdm: - image: ghcr.io/mitre/iat/iwp-pdm:latest - container_name: pdm - restart: always - depends_on: - - "activemq" - - mysql: - image: mysql:8 - container_name: iwp_mysql - restart: always - environment: - # MYSQL_DATABASE: 'iwp' - MYSQL_DATABASE: iwp - # So you don't have to use root, but you can if you like - MYSQL_USER: ${DATABASE_USERNAME} - # You can use whatever password you like - MYSQL_PASSWORD: ${DATABASE_PASSWORD} - # Password for root access - MYSQL_ROOT_PASSWORD: ${DATABASE_ROOT_PASSWORD} - MYSQL_ROOT_HOST: '%' - healthcheck: - test: mysqladmin ping -h localhost -u $$DATABASE_USERNAME --password=$$DATABASE_PASSWORD - interval: 1s - timeout: 1s - retries: 50 - ports: - # : < MySQL Port running inside container> - - '3306:3306' - expose: - # Opens port 3306 on the container - - '3306' - # Where our data will be persisted - volumes: - - my-db:/var/lib/mysql - security_opt: - - seccomp:unconfined - env_file: .env - -volumes: - my-db: diff --git a/iris-workstation/iwp-simple/iwp/config/application.properties b/iris-workstation/iwp-simple/iwp/config/application.properties deleted file mode 100644 index caa6424..0000000 --- a/iris-workstation/iwp-simple/iwp/config/application.properties +++ /dev/null @@ -1,142 +0,0 @@ -# -# NOTICE -# -# This software (or technical data) was produced for the U. S. Government -# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV -# (May 2014) – Alternative IV (Dec 2007) -# -# (c) 2024 The MITRE Corporation. All Rights Reserved. -# - -# ActiveMQ services available -spring.activemq.broker-url=tcp://activemq:61616 -server.error.whitelabel.enabled=false - -iris.biqtService=true -iris.aciiService=true -iris.dmipaService=false -iris.tshepiiService=true -iris.dualPDMService=true -iris.contactService=true -iris.pdmService=true - -iris.aciiQueueRequest=iris.acii.request -iris.aciiQueueResponse=iris.acii.response -iris.biqtQueueRequest=iris.biqt.request -iris.biqtQueueResponse=iris.biqt.response -iris.tshepiiQueueRequest=iris.tshepii.request -iris.tshepiiQueueResponse=iris.tshepii.response -iris.contactQueueResponse=iris.contactdetection.response -iris.irisAnnotationQueueRequest=iris.annotation.request -iris.irisAnnotationQueueResponse=iris.annotation.response -iris.pdmQueueRequest=iris.pdm.request -iris.pdmQueueResponse=iris.pdm.response -iris.dualPDMQueueRequest=iris.dual.pdm.request -iris.dualPDMQueueResponse=iris.dual.pdm.response - -iris.aciiServiceName=acii -iris.biqtServiceName=biqt -iris.tshepiiServiceName=tshepii -iris.irisAnnotationServiceName=annotation -iris.dualPDMServiceName=dualpdm -iris.pdmServiceName=pdm - -# Services addresses -iris.activeMqAddress=tcp://activemq:61616 -iris.iwpWebAddress=http://localhost:8080 -iris.activeMqHttpAddress=http://activemq:8161/admin/xml/queues.jsp -iris.activeMqUserName=admin -iris.activeMqPassword=admin - -# Type 1 Record defaults - -#server.port=8080 -#server.ssl.enabled=false - -#Spring Data -spring.jpa.hibernate.ddl-auto=update -spring.jpa.hibernate.use-new-id-generator-mappings=false -spring.datasource.url=jdbc:mysql://iwp_mysql:3306/iwp?allowPublicKeyRetrieval=TRUE&useSSL=FALSE -spring.datasource.username=${DATABASE_USERNAME:iwp} -spring.datasource.password=${DATABASE_PASSWORD:iwp} - -#merv -spring.jpa.show-sql=false -spring.datasource.driverClassName=com.mysql.jdbc.Driver - -# Security Options - -# iwp.security.secured:boolean -# ----> Whether the application should respect @Secured annotations and use a login page -iwp.security.secured=false - -#The below user password hashes are encrypted and are decrypted at runtime with the password "examplepass" -#You can use this command to decrypt it with the standalone jayspt tool. A very similar command will allow you to encrypt things. -#Just change ./decrypt.sh to ./encrypt.sh and change input to be what you want to encrypt. -#The additional options are needed because the spring jayspt uses those defaults to decrypt and it will break without them. -#When encryping a hash on the commandline you need to escape the $ characters with a \ -#./decrypt.sh input="STRINGTODECRYPT" password=examplepass algorithm=PBEWITHHMACSHA512ANDAES_256 verbose=true stringOutputType=base64 providerName=SunJCE saltGeneratorClassName=org.jasypt.salt.RandomSaltGenerator ivGeneratorClassName=org.jasypt.iv.RandomIvGenerator -#./encrypt.sh input="STRINGTOENCRYPT" password=examplepass algorithm=PBEWITHHMACSHA512ANDAES_256 verbose=true stringOutputType=base64 providerName=SunJCE saltGeneratorClassName=org.jasypt.salt.RandomSaltGenerator ivGeneratorClassName=org.jasypt.iv.RandomIvGenerator - -#You can download these tools here (http://www.jasypt.org/cli.html). If the ZIP download is not available, check the jasypt Github at (https://github.com/jasypt/jasypt/releases). - -iwp.users.defaultAdminFullName=Admin -iwp.users.defaultAdminTitle="Mr Admin" -iwp.users.defaultAdminEmail=test@iwp.org -iwp.users.defaultAdminUsername=default_admin -#password: test -#$2a$10$ofhKLcbYr51tcmOfP1aGMeHfKGl7AqixKfWmFEVTBnd3eba2uTd3i -iwp.users.defaultAdminPassword=ENC(PqXjWjyhuCE7xf23n2DtMx0PWBzQSbHti+q6XSKEmSi0LFVMl/9ZGbl1KVYQAfss6r5BlVwQhlNhVO3xPWp4nGOHcjh+dctLtdfjx97WCXXtUlzTx7tYv85uDEVwvyqx) - -iwp.users.defaultReviewerFullName=Reviewer -iwp.users.defaultReviewerTitle="Mr Reviewer" -iwp.users.defaultReviewerEmail=test@iwp.org -iwp.users.defaultReviewerUsername=default_reviewer -#password: review -#$2a$10$XjunjcVzjKDXuzP0fHb.teL/rFJUxUPG1uhZRP/GTLpj5M8mlDT8O -iwp.users.defaultReviewerPassword=ENC(kW9PCWg368pSWkQYGG4QZXUcSWFMAgoa8lcYx+ste51qQQAgh5wpx4vTJRMMU5P0hVHlgewb/Tbld+ER6ddJaLzvIwzVGd/3OaO0QTgGrrXwy0XYnWadZnlYKhAq8db+) - -iwp.users.defaultSupervisorFullName=Supervisor -iwp.users.defaultSupervisorTitle="Mr Supervisor" -iwp.users.defaultSupervisorEmail=test@iwp.org -iwp.users.defaultSupervisorUsername=default_supervisor -#password: supervisor -#$2a$10$hCDbbdZDoM8PhDucs38XeOizKoZZ7WTKnS5Efpqtgm/Q.iHBRhI7. -iwp.users.defaultSupervisorPassword=ENC(b4McYSdVV7M1AcT1N5D3nrPhKmJZwV1zyKXcrqJLe/tYUegZwjnIy+Ej+8eW90p+i3XM3fQpEfIHRkvXys1cNNVqMKJs7JrdVWGlECYykyznRqNOkc9zKPhFmW9myk5r) - - -iwp.upload.path=/app/uploads -iwp.image.path=/app/images - -#server.ssl.key-store-password= - -# -# Iris Annotation Properties -# -iris.annotations.0=Unknown -iris.annotations.1=Iris -iris.annotations.2=Eyelash -iris.annotations.3=Sclera -iris.annotations.4=Pupil -iris.annotations.5=Eyebrow -iris.annotations.6=Highlight -iris.annotations.7=Border -iris.annotations.8=Crypt -iris.annotations.9=Ridge -iris.annotations.10=Contact - -iris.annotationsColorHex.0=#CCCCCC -iris.annotationsColorHex.1=#0000FF -iris.annotationsColorHex.2=#FF0000 -iris.annotationsColorHex.3=#FFFFFF -iris.annotationsColorHex.4=#000000 -iris.annotationsColorHex.5=#00FFFF -iris.annotationsColorHex.6=#FFFF00 -iris.annotationsColorHex.7=#FF00FF -iris.annotationsColorHex.8=#33FFFF -iris.annotationsColorHex.9=#FF9933 -iris.annotationsColorHex.10=#00A5FF - -iris.adjudicationValue.0=Match,true -iris.adjudicationValue.1=Exclude,false -iris.adjudicationValue.2=Inconclusive,true diff --git a/iwp-script.sh b/iwp-script.sh new file mode 100755 index 0000000..622e26f --- /dev/null +++ b/iwp-script.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +IAT_DIR="$(cd "$(dirname "$0")" && pwd)" +IW_DIR="${IAT_DIR}/web" +IW_TARGET_SRC="${IW_DIR}/target" +IW_JAR_NAME="web-2.0.0.jar" +IW_ENV_FILE="${IAT_DIR}/docker/.env" + + +build() { + echo "Building the iris workstation..." + cd "$IAT_DIR" || exit 1 + mvn clean package -DskipTests +} + +killIrisWs() { + echo "Killing the iris workstation..." + ps -ef | grep "[j]ava -jar ${IW_JAR_NAME}" + ps -ef | grep "[j]ava -jar ${IW_JAR_NAME}" | awk '{ print $2 }' | xargs -I {} kill {} +} + +startService() { + echo + echo "Starting service..." + + if [[ ! -d "$IW_TARGET_SRC" ]]; then + echo "Creating ${IW_TARGET_SRC}" + mkdir -p "$IW_TARGET_SRC" || exit 1 + fi + + cd "$IW_TARGET_SRC" || exit 1 + + if [[ -f "$IW_ENV_FILE" ]]; then + echo "Loading environment from ${IW_ENV_FILE}" + IW_PRESET_JASYPT_ENCRYPTOR_PASSWORD="${JASYPT_ENCRYPTOR_PASSWORD}" + set -a + source "$IW_ENV_FILE" + set +a + if [[ -z "$JASYPT_ENCRYPTOR_PASSWORD" && -n "$IW_PRESET_JASYPT_ENCRYPTOR_PASSWORD" ]]; then + export JASYPT_ENCRYPTOR_PASSWORD="$IW_PRESET_JASYPT_ENCRYPTOR_PASSWORD" + fi + else + echo "Unable to find ${IW_ENV_FILE}. Create it from docker/.env.template before starting." + exit 1 + fi + + # Jasypt expects JASYPT_ENCRYPTOR_PASSWORD to contain the decryption password. + # Passing the password on the command line can expose it to process-listing tools. + # To run without a prompt or command-line password, set this environment variable: + # export JASYPT_ENCRYPTOR_PASSWORD="yourpass" + + # Use JASYPT_ENCRYPTOR_PASSWORD from the argument when it is provided. + if [[ -n "$1" ]]; then + echo "Using JASYPT_ENCRYPTOR_PASSWORD passed from the command line" + export JASYPT_ENCRYPTOR_PASSWORD=$1 + + # Otherwise, use JASYPT_ENCRYPTOR_PASSWORD from the environment when it is set. + elif [[ -n "$JASYPT_ENCRYPTOR_PASSWORD" ]]; then + echo "Using JASYPT_ENCRYPTOR_PASSWORD from environment variable" + # If JASYPT_ENCRYPTOR_PASSWORD is not set or provided, prompt for it. + else + echo "Enter jasypt encryption password: " + read -s JASYPT_ENCRYPTOR_PASSWORD + export JASYPT_ENCRYPTOR_PASSWORD + fi + + if [[ ! -f "$IW_JAR_NAME" ]]; then + echo "Unable to find ${IW_JAR_NAME}. Run './iwp-script.sh build' first." + exit 1 + fi + + nohup java -jar "$IW_JAR_NAME" > /tmp/iwp-web.log & + tail -f /tmp/iwp-web.log +} + +pauseForKill() { + echo "Sleeping for .5 seconds" + sleep .5 +} + +case $1 in + build) + build + ;; + kill) + killIrisWs + ;; + full) + killIrisWs && build && startService $2 + ;; + start) + startService $2 + ;; + restart) + killIrisWs && pauseForKill && startService + ;; + help) + echo "Arguments [build|kill|full|start|restart] [Jasypt encryption password]" + ;; + *) + echo "Unknown argument [build|kill|full|start|restart] [Jasypt encryption password]" +esac diff --git a/pdm/.env.template b/pdm/.env.template new file mode 100644 index 0000000..e94d0ae --- /dev/null +++ b/pdm/.env.template @@ -0,0 +1,9 @@ +# Required Artemis credentials +ARTEMIS_USER= +ARTEMIS_PASSWORD= + +# STOMP broker endpoint for a native run +STOMP_HOST=localhost:61613 + +# Optional STOMP heartbeats (outgoing,incoming), in milliseconds +STOMP_HEARTBEATS=10000,0 diff --git a/pdm/BiomechDeformer.py b/pdm/BiomechDeformer.py new file mode 100644 index 0000000..306a892 --- /dev/null +++ b/pdm/BiomechDeformer.py @@ -0,0 +1,251 @@ +import torch +from torch.nn.functional import grid_sample +from torchvision.transforms import ToTensor +import numpy as np +import torch +import torch.nn as nn +from PIL import Image +import math + +class conv(nn.Module): + def __init__(self, in_channels=512, out_n = 6): + super().__init__() + self.conv = nn.Conv2d(in_channels, out_n, kernel_size=1, stride=1, padding='same') + def forward(self, x): + x = self.conv(x) + return x + +class fclayer(nn.Module): + def __init__(self, in_h = 8, in_w = 10, out_n = 6): + super().__init__() + self.in_h = in_h + self.in_w = in_w + self.out_n = out_n + self.fc_list = [] + for i in range(out_n): + self.fc_list.append(nn.Linear(in_h*in_w, 1)) + self.fc_list = nn.ModuleList(self.fc_list) + def forward(self, x): + x = x.reshape(-1, 6, self.in_h, self.in_w) + outs = [] + for i in range(self.out_n): + outs.append(self.fc_list[i](x[:, i, :, :].reshape(-1, self.in_h*self.in_w))) + out = torch.cat(outs, 1) + return out + +class BiomechDeformer(object): + def __init__(self, device=torch.device('cpu')): + self.device = device + + def quadSolve(self, A, B, C): + return ((-1 * B - torch.sqrt(B * B - 4 * A * C)) / (2 * A)) + + def getAngle(self, y, x): + with torch.no_grad(): + theta = torch.atan2(y, x) + beta = theta + 2 * math.pi + return torch.where(beta < 2 * math.pi, beta, beta - 2 * math.pi) + + def find_xy_less(self, r1, r1p, xp, yp, xc, yc): + x = ((r1 * (xp - xc)) / r1p) + xc + y = ((r1 * (yp - yc)) / r1p) + yc + return x, y + + def Biomech(self, ri0, ri1, numVertices): + + ro = .006 + + vertices = np.linspace((ri1/ro),(ro/ro),numVertices) + h = vertices[2]-vertices[1] + + elements = [] + + boundaries = np.zeros((len(vertices), len(vertices))) + + Er = 4000 + Et = 2970 + nu = 0.49 # The Poisson variable. + + zeta = Et / Er + + eta = Er / (1 - zeta * (nu ** 2)) + + chi = Et / (1 - zeta * (nu ** 2)) + + U = np.zeros((len(vertices),1)) + U_linear = U + + resThresh = 1e-7 + + eta2 = ri1/ro #Normalized final radius. This is different from the eta with the material parameters. + lamda = (ri1-ri0)/ro #This might be need to be modified to incorporate the various changes. This isn't quite fully the equation. + + for q in range(100): + K = np.zeros((len(vertices), len(vertices))) + K[0,0] = 1 + K[len(vertices)-1,len(vertices)-1] = 1 + + f = np.zeros((len(vertices),1)) + f[0] = 1 + f[len(vertices)-1] = 0 + + for i in range(1, len(vertices)-1): + Umid = np.array([(U[i-1]+U[i])/2, (U[i]+U[i+1])/2]) + dU_dr = np.array([(U[i]-U[i-1])/(h), (U[i+1]-U[i])/(h)]) + + for j in range(i-1, i+2): + + start = max(i-1,j-1) + stop = min(i,j) + if start <= stop: + step = 1 + else: + step = -1 + for k in range(start, stop+1, step): + + basis_i = 0.5 + basis_j = 0.5 + + if k == i-1: + dbi_dr = 1/h + else: + dbi_dr = -1/h + + if k == j-1: + dbj_dr = 1/h + else: + dbj_dr = -1/h + + r = vertices[k]+h/2 + + k_local = k - (i-1) + + omega = (nu * eta * zeta - chi) / r + psi = (eta - nu * chi) / r + + alpha = eta / r + + beta = -chi / (r * r) + + Kijk = basis_i * basis_j * (Umid[k_local] * (lamda*(nu+1)*zeta/(2*r**3)) - zeta/r**2) + \ + basis_i * dbj_dr * (dU_dr[k_local]*(-(1-nu*zeta)/(2*r)) - Umid[k_local] * (nu*zeta*lamda/r**2) + 1/r) + \ + dbi_dr * dbj_dr * (-1 + lamda/2*dU_dr[k_local]) + K[i,j] = K[i,j] + h * Kijk + + res = np.linalg.norm(K @ U - f) + if (res < resThresh): + break + + Unew = np.linalg.inv(K) @ f + U = U + (Unew - U) + + if np.linalg.norm(U_linear) == 0: + U_linear = U + + UR = U * (ri1 - ri0) + R = np.linspace(ri0, ro, numVertices) + UR.reshape(numVertices) + normR = (R - R.min()) / (R.max() - R.min()) + return normR + + def do_biomech_deform(self, image, mask, pupil_xyr, iris_xyr, target_alpha, width, height): + with torch.no_grad(): + pupil_xyr = torch.tensor(pupil_xyr).float().to(self.device) + iris_xyr = torch.tensor(iris_xyr).float().to(self.device) + + polar_height = round(math.sqrt((pupil_xyr[0].item() - iris_xyr[0].item()) ** 2 + (pupil_xyr[1].item() - iris_xyr[1].item()) ** 2) + (iris_xyr[2].item() - pupil_xyr[2].item())) + polar_width = round(2 * math.pi * iris_xyr[2].item()) + + theta = (2*math.pi*torch.linspace(0,polar_width,polar_width)/polar_width).to(self.device) + pxCirclePoints = pupil_xyr[0] + pupil_xyr[2] * torch.cos(theta).to(self.device) # 512 + pyCirclePoints = pupil_xyr[1] + pupil_xyr[2] * torch.sin(theta).to(self.device) # 512 + + ixCirclePoints = iris_xyr[0] + iris_xyr[2] * torch.cos(theta).to(self.device) # 512 + iyCirclePoints = iris_xyr[1] + iris_xyr[2] * torch.sin(theta).to(self.device) # 512 + + pr = float(pupil_xyr[2].item()) / float(iris_xyr[2].item()) + if pr > target_alpha: # constriction + radiusBiomech = self.Biomech(target_alpha * 0.006, pr * 0.006, polar_height+1)[1:] + radius = 2 * (torch.linspace(1,polar_height,polar_height)/polar_height).reshape(-1, 1).to(self.device) - torch.tensor(radiusBiomech).float().reshape(-1, 1).to(self.device) + else: # dilation + radiusBiomech = self.Biomech(pr * 0.006, target_alpha * 0.006, polar_height+1)[1:] + radius = torch.tensor(radiusBiomech).float().reshape(-1, 1).to(self.device) + + radius = torch.tensor(radiusBiomech).float().reshape(-1, 1).to(self.device) + + pxCoords = torch.matmul((1.0-radius), pxCirclePoints.reshape(1, polar_width)) # 64 x 512 + pyCoords = torch.matmul((1.0-radius), pyCirclePoints.reshape(1, polar_width)) # 64 x 512 + + ixCoords = torch.matmul(radius, ixCirclePoints.reshape(1, polar_width)) # 64 x 512 + iyCoords = torch.matmul(radius, iyCirclePoints.reshape(1, polar_width)) # 64 x 512 + + grid_x_cartToPol = torch.clamp(pxCoords + ixCoords, 0, width-1).float().to(self.device) # 1 x 1 x 64 x 512 + grid_y_cartToPol = torch.clamp(pyCoords + iyCoords, 0, height-1).float().to(self.device) # 1 x 1 x 64 x 512 + + Y = torch.arange(0, height).reshape(height, 1).repeat(1, width).float().to(self.device) + X = torch.arange(0, width).reshape(1, width).repeat(height, 1).float().to(self.device) + + xcpr = torch.tensor(pupil_xyr[0].item()).reshape(1, 1).repeat(height, width).to(self.device) + ycpr = torch.tensor(pupil_xyr[1].item()).reshape(1, 1).repeat(height, width).to(self.device) + + dists_p = torch.sqrt((X - xcpr) ** 2 + (Y - ycpr) ** 2).to(self.device) + + xcir = torch.tensor(iris_xyr[0].item()).reshape(1, 1).repeat(height, width).to(self.device) + ycir = torch.tensor(iris_xyr[1].item()).reshape(1, 1).repeat(height, width).to(self.device) + + dists_i = torch.sqrt((X - xcir) ** 2 + (Y - ycir) ** 2).to(self.device) + + xy_between_cond = torch.where(torch.logical_and(dists_p > target_alpha * iris_xyr[2].item(), dists_i < int(iris_xyr[2].item())), 1.0, 0.0).to(self.device) + + a = (pupil_xyr[0].item() - iris_xyr[0].item()) ** 2 + (pupil_xyr[1].item() - iris_xyr[1].item()) ** 2 - (target_alpha * iris_xyr[2].item() - iris_xyr[2].item()) ** 2 + b_part = 2 * (target_alpha * iris_xyr[2].item()) * (iris_xyr[2].item() - target_alpha * iris_xyr[2].item()) + c_part = (target_alpha * iris_xyr[2].item()) ** 2 + + A = torch.tensor(a).reshape(1, 1).repeat(height, width).to(self.device) + B = 2 * (X - pupil_xyr[0].item()) * (pupil_xyr[0].item() - iris_xyr[0].item()) + 2 * (Y - pupil_xyr[1].item()) * (pupil_xyr[1].item() - iris_xyr[1].item()) - b_part + C = torch.pow((X - pupil_xyr[0].item()), 2) + torch.pow((Y - pupil_xyr[1].item()), 2) - c_part + r = self.quadSolve(A, B, C).real.float().to(self.device) + X_c = r * iris_xyr[0].item() + (1.0 - r) * pupil_xyr[0].item() + Y_c = r * iris_xyr[1].item() + (1.0 - r) * pupil_xyr[1].item() + grid_x_polToCart = (self.getAngle(Y - Y_c, X - X_c) / (2*math.pi)).float().to(self.device) * (polar_width-1) * xy_between_cond + grid_y_polToCart = r.to(self.device) * (polar_height-1) * xy_between_cond + + grid_sample_mat_polToCart = torch.cat([grid_x_polToCart.unsqueeze(-1), grid_y_polToCart.unsqueeze(-1)], dim=-1).unsqueeze(0).to(self.device) + grid_sample_mat_cartToPol = torch.cat([grid_x_cartToPol.unsqueeze(-1), grid_y_cartToPol.unsqueeze(-1)], dim=-1).unsqueeze(0).to(self.device) + + image_polar = self.deform(image, width, height, grid_sample_mat_cartToPol, interp_mode='bicubic') + image_deformed = self.deform(image_polar, polar_width, polar_height, grid_sample_mat_polToCart, interp_mode='bicubic') + + r1r = torch.tensor(pupil_xyr[2].item()).reshape(-1, 1, 1).repeat(1, height, width).to(self.device) + r1pr = torch.tensor(target_alpha * iris_xyr[2].item()).reshape(-1, 1, 1).repeat(1, height, width).to(self.device) + + x_less, y_less = self.find_xy_less(r1r, r1pr, X, Y, xcpr, ycpr) + xy_less = torch.cat((x_less.unsqueeze(3), y_less.unsqueeze(3)), dim=3).float() + + mask_deformed = self.deform(mask.float(), width, height, xy_less, interp_mode='bicubic') + pupil_mask_deformed = torch.where(dists_p < 1.1 * target_alpha * iris_xyr[2].item(), mask_deformed, torch.ones(mask_deformed.shape).float().to(self.device)) + + image_deformed = torch.where(dists_i >= int(iris_xyr[2].item())-1, image, image_deformed) * pupil_mask_deformed + + return image_deformed + + def deform(self, input, W, H, grid, interp_mode): #helper function + with torch.no_grad(): + # grid: [-1, 1] + gridx = grid[:, :, :, 0] + gridy = grid[:, :, :, 1] + gridx = gridx / (W - 1) + gridx = (gridx - 0.5) * 2 + gridy = gridy / (H - 1) + gridy = (gridy - 0.5) * 2 + newgrid = torch.stack([gridx, gridy], dim=-1) + return grid_sample(input, newgrid, mode=interp_mode, align_corners=True) + + def biomechanical_deform(self, image, mask, pupil_xyr, iris_xyr, alpha): + with torch.no_grad(): + image = ToTensor()(image).unsqueeze(0).to(self.device) * 255 + width = image.shape[3] + height = image.shape[2] + mask_t = ToTensor()(mask).unsqueeze(0).to(self.device) + deformed_image = self.do_biomech_deform(image, mask_t, pupil_xyr, iris_xyr, alpha, width, height) + deformed_image = torch.clamp(torch.round(deformed_image), min=0, max=255) + return Image.fromarray(deformed_image[0][0].cpu().numpy().astype(np.uint8), 'L') \ No newline at end of file diff --git a/pdm/CircleMaskFinder.py b/pdm/CircleMaskFinder.py new file mode 100644 index 0000000..e12755d --- /dev/null +++ b/pdm/CircleMaskFinder.py @@ -0,0 +1,128 @@ +import math +import torch +import numpy as np +import cv2 +import torch +import torch.nn as nn +from torchvision import models +from torchvision.transforms import Compose, ToTensor, Normalize +from network import NestedSharedAtrousResUNet, NestedSharedAtrousAttentionResUNetIN +from PIL import Image + +class fclayer(nn.Module): + def __init__(self, in_h = 8, in_w = 10, out_n = 6): + super().__init__() + self.in_h = in_h + self.in_w = in_w + self.out_n = out_n + self.fc_list = [] + self.fc_list = nn.ModuleList(nn.Linear(in_h*in_w, 1) for i in range(out_n)) + def forward(self, x): + x = x.reshape(-1, self.out_n, self.in_h, self.in_w) + outs = [] + for i, fc_layer in enumerate(self.fc_list): + outs.append(fc_layer(x[:, i, :, :].reshape(-1, self.in_h*self.in_w))) + out = torch.cat(outs, 1) + return out + +class conv(nn.Module): + def __init__(self, in_channels=512, out_n = 6): + super().__init__() + self.conv = nn.Conv2d(in_channels, out_n, kernel_size=1, stride=1, padding='same') + def forward(self, x): + x = self.conv(x) + return x + +class CircleMaskFinder(object): + def __init__(self, mask_net_path = "./models/nestedsharedatrousresunet-156-0.026496-maskIoU-0.943222.pth", circle_net_path = './models/convnext_tiny-1076-0.030622-maskIoU-0.938355.pth', eyelid_net_path = "./models/nestedsharedatrousresunet-1935-0.036982-maskIoU-0.946174-insideeyelid.pth", device=torch.device('cpu')): + self.circle_net_path = circle_net_path + self.mask_net_path = mask_net_path + self.eyelid_net_path = eyelid_net_path + self.device = device + self.NET_INPUT_SIZE = (320,240) + self.eyelid_model = NestedSharedAtrousResUNet(1, 1, width=64, resolution=(self.NET_INPUT_SIZE[1], self.NET_INPUT_SIZE[0])) + try: + self.eyelid_model.load_state_dict(torch.load(self.eyelid_net_path, map_location=self.device)) + except AssertionError: + print("assertion error") + self.eyelid_model.load_state_dict(torch.load(self.eyelid_net_path, + map_location = lambda storage, loc: storage)) + self.eyelid_model = self.eyelid_model.to(self.device) + self.eyelid_model.eval() + self.circle_model = models.convnext_tiny() + self.circle_model.avgpool = conv(in_channels=768, out_n=6) + self.circle_model.classifier = fclayer(in_h=7, in_w=10, out_n=6) + try: + self.circle_model.load_state_dict(torch.load(self.circle_net_path, map_location=self.device)) + except AssertionError: + print("assertion error") + self.circle_model.load_state_dict(torch.load(self.circle_net_path, + map_location = lambda storage, loc: storage)) + self.circle_model = self.circle_model.to(self.device) + self.circle_model.eval() + self.mask_model = NestedSharedAtrousResUNet(1, 1, width=64, resolution=(self.NET_INPUT_SIZE[1], self.NET_INPUT_SIZE[0])) + try: + self.mask_model.load_state_dict(torch.load(self.mask_net_path, map_location=self.device)) + except AssertionError: + print("assertion error") + self.mask_model.load_state_dict(torch.load(self.mask_net_path, + map_location = lambda storage, loc: storage)) + self.mask_model = self.mask_model.to(self.device) + self.mask_model.eval() + self.input_transform = Compose([ + ToTensor(), + Normalize(mean=(0.5,), std=(0.5,)) + ]) + self.ISO_RES = (640,480) + + def fix_image(self, image): + w, h = image.size + aspect_ratio = float(w)/float(h) + if aspect_ratio >= 1.333 and aspect_ratio <= 1.334: + result_im = image + w_pad = 0 + h_pad = 0 + elif aspect_ratio < 1.333: + w_new = h * (4.0/3.0) + w_pad = (w_new - w) / 2 + result_im = Image.new(image.mode, (int(w_new), h), 127) + result_im.paste(image, (int(w_pad), 0)) + h_pad = 0 + else: + h_new = w * (3.0/4.0) + h_pad = (h_new - h) / 2 + result_im = Image.new(image.mode, (w, int(h_new)), 127) + result_im.paste(image, (0, int(h_pad))) + w_pad = 0 + return result_im, w_pad, h_pad + + def segmentAndCircApprox(self, image): + + w,h = image.size + + image = cv2.resize(np.array(image), self.NET_INPUT_SIZE, cv2.INTER_LINEAR_EXACT) + with torch.inference_mode(): + inp_t = self.input_transform(image).unsqueeze(0).to(self.device) + mask_logit_t = torch.sigmoid(self.mask_model(inp_t)) + eyelid_logit_t = torch.sigmoid(self.eyelid_model(inp_t)) + back_mask_t = torch.where(eyelid_logit_t > 0.5, 255, 0) + back_mask = back_mask_t.cpu().numpy()[0][0] + mask_t = torch.where(mask_logit_t > 0.5, 255, 0) + mask = mask_t.cpu().numpy()[0][0] + inp_xyr_t = self.circle_model(inp_t.repeat(1,3,1,1)) + inp_xyr = inp_xyr_t.tolist()[0] + + #Mask + mask = cv2.resize(mask, (w, h), interpolation=cv2.INTER_NEAREST_EXACT) + back_mask = cv2.resize(back_mask, (w, h), interpolation=cv2.INTER_NEAREST_EXACT) + + #Circle params + diag = math.sqrt(w**2 + h**2) + pupil_x = inp_xyr[0] * w + pupil_y = inp_xyr[1] * h + pupil_r = inp_xyr[2] * 0.5 * 0.8 * diag + iris_x = inp_xyr[3] * w + iris_y = inp_xyr[4] * h + iris_r = inp_xyr[5] * 0.5 * diag + + return Image.fromarray(mask.astype(np.uint8), "L"), Image.fromarray(back_mask.astype(np.uint8), "L"), np.array([pupil_x,pupil_y,pupil_r]), np.array([iris_x,iris_y,iris_r]) \ No newline at end of file diff --git a/pdm/Comparison_GUI.py b/pdm/Comparison_GUI.py new file mode 100644 index 0000000..ac0d85f --- /dev/null +++ b/pdm/Comparison_GUI.py @@ -0,0 +1,583 @@ +from PIL import Image, UnidentifiedImageError +import torch +import sys +import time +from modules.irisRecognition import irisRecognition +from modules.utils import get_cfg +import argparse +import os +from LinearDeformer import LinearDeformer +from BiomechDeformer import BiomechDeformer +from CircleMaskFinder import CircleMaskFinder +from EyePreserve import EyePreserve +import numpy as np +import Pdm_pb2 +import Iwp_pb2 +import stomp +import logging +import io +from typing import Optional, Tuple +from concurrent.futures import ThreadPoolExecutor + +import warnings +warnings.filterwarnings("ignore", category=FutureWarning) + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +PDM_REQUEST_QUEUE = "iris.pdm.request" +PDM_RESPONSE_QUEUE = "iris.pdm.response" +DUAL_PDM_REQUEST_QUEUE = "iris.dual.pdm.request" +DUAL_PDM_RESPONSE_QUEUE = "iris.dual.pdm.response" +ARTEMIS_ANYCAST_HEADERS = {"destination-type": "ANYCAST"} +ARTEMIS_ANYCAST_SUBSCRIBE_HEADERS = {"subscription-type": "ANYCAST"} + +def stomp_host() -> Tuple[str, int]: + host_and_port = os.getenv("STOMP_HOST") + if host_and_port: + if ":" in host_and_port: + host, port = host_and_port.rsplit(":", 1) + else: + host, port = host_and_port, "61613" + else: + host = os.getenv("ACTIVEMQ_HOST", "activemq") + port = os.getenv("ACTIVEMQ_PORT", "61613") + + return host or "activemq", int(port) + +def artemis_credentials() -> Tuple[str, str]: + username = os.getenv("ARTEMIS_USER") + password = os.getenv("ARTEMIS_PASSWORD") + + if not username or not password: + raise RuntimeError(f"Missing required Artemis credentials") + + return username, password + +def stomp_heartbeats() -> Optional[Tuple[int, int]]: + heartbeats = os.getenv("STOMP_HEARTBEATS") + if not heartbeats: + return None + + try: + outgoing, incoming = heartbeats.split(",", 1) + return int(outgoing), int(incoming) + except ValueError: + logger.warning("Invalid STOMP_HEARTBEATS value %r; using stomp.py defaults.", heartbeats) + return None + +def normalize_destination(destination: Optional[str]) -> Optional[str]: + if destination and destination.startswith("/queue/"): + return destination.removeprefix("/queue/") + return destination + +## Helper functions +def get_concat_h(images): + widths = [] + width = 0 + for img in images: + widths.append(width) + width += img.width + height = images[0].height + dst = Image.new('RGB', (width, height)) + for i, img in enumerate(images): + dst.paste(img, (widths[i], 0)) + return dst + +def get_concat_h_cut_center(im1, im2): + dst = Image.new('RGB', (im1.width + im2.width, min(im1.height, im2.height))) + dst.paste(im1, (0, 0)) + dst.paste(im2, (im1.width, (im1.height - im2.height) // 2)) + return dst + +def progressbar(it, prefix="", size=60, out=sys.stdout): # Python3.6+ + count = len(it) + start = time.time() # time estimate start + def show(j): + x = int(size*j/count) + # time estimate calculation and string + remaining = ((time.time() - start) / j) * (count - j) + mins, sec = divmod(remaining, 60) # limited to minutes + time_str = f"{int(mins):02}:{sec:03.1f}" + print(f"{prefix}[{u'█'*x}{('.'*(size-x))}] {j}/{count} Est wait {time_str}", end='\r', file=out, flush=True) + show(0.1) # avoid div/0 + for i, item in enumerate(it): + yield item + show(i+1) + print("\n", flush=True, file=out) + +## Stores all the models and all the deformation results + +class DeformerWrapper: + def __init__(self, wide_size, square_size, device): + super().__init__() + self.device = device + self.circlemaskfinder = CircleMaskFinder(mask_net_path = "./models/nestedsharedatrousresunet-156-0.026496-maskIoU-0.943222.pth", circle_net_path = './models/convnext_tiny-1076-0.030622-maskIoU-0.938355.pth', device=device) + self.ldeformer = LinearDeformer(device=device) + self.bdeformer = BiomechDeformer(device=device) + self.eyepreserve = EyePreserve(net_path="./models/0007-val_loss-22.957276336365826+-0.0-val_bit_match-81.10588183031244+-0.0-val_linear_bit_match-75.13033321984577+-0.0.pth", device=device) + self.irisRec = irisRecognition(get_cfg('./cfg.yaml'), use_hough=False) + + self.square_size = square_size + self.wide_size = wide_size + + self.ir_ratio = 16/14 + self.vis_frac_hal = 0.2 + self.vis_frac_border = 0.5 + + self.dnet_im1_alpha_crop = {} + self.dnet_im1_alpha_nocrop = {} + self.dnet_im1_alpha_score = {} + + self.converted = False + self.im1 = None + self.im2 = None + + def load_images(self, image_data): + try: + # Load image from byte array + self.im1 = Image.open(io.BytesIO(image_data)) + logging.info(f"Given image has mode {self.im1.mode} {self.converted}") + + if self.im1.mode == "I;16": + self.im1 = self.convert_16bit_gray_to_rgb(self.im1) + self.converted = True + else: + logging.info(f"Image is {self.im1.mode}") + + logging.info("Image loaded successfully from byte array") + except UnidentifiedImageError: + logging.error("Failed to open the image from byte array") + except Exception as e: + logging.error(f"An unexpected error occurred while loading the image: {e}", exc_info=True) + + + def load_dual_images(self, probe_data, candidate_data): + logging.info(f"Probe and Candidate are of type: {type(probe_data), type(candidate_data)}") + + try: + self.im1 = Image.open(io.BytesIO(probe_data)) + self.im2 = Image.open(io.BytesIO(candidate_data)) + + logging.info("Successfully uploaded probe and candidate") + + except UnidentifiedImageError: + logging.error("Failed to open the DUAL image from byte array") + except Exception as e: + logging.error(f"An unexpected error occurred while loading the DUAL images: {e}", exc_info=True) + + def convert_16bit_gray_to_rgb(self, image): + # Convert image to numpy array + gray_array = np.array(image, dtype=np.float32) + + # Normalize 16-bit values to [0, 1] + normalized_gray = gray_array / 65535.0 + + # Apply gamma correction (linearize) + linear_gray = np.power(normalized_gray, 2.2) + + # Expand to RGB + linear_rgb = np.stack([linear_gray] * 3, axis=-1) + + # Encode gamma (apply sRGB gamma) + encoded_rgb = np.power(linear_rgb, 1/2.2) + + # Scale back to [0, 255] and convert to uint8 + rgb_array = (encoded_rgb * 255).astype(np.uint8) + + # Convert back to PIL Image + rgb_image = Image.fromarray(rgb_array, mode="RGB") + + return rgb_image + + def findMaskAndCircles(self, image): + # Ensures input image has a 4:3 aspect ratio by padding with gray pixels + image, self.w_pad, self.h_pad = self.circlemaskfinder.fix_image(image) + + # Image segmentation into foreground, background, pupil data, iris data + mask, back_mask, pxyr, ixyr = self.circlemaskfinder.segmentAndCircApprox(image.convert("L")) + alpha = pxyr[2]/ixyr[2] + image_crop = image.crop((ixyr[0] - int(round(self.ir_ratio*ixyr[2])), ixyr[1] - int(round(self.ir_ratio*ixyr[2])), ixyr[0] + int(round(self.ir_ratio*ixyr[2])), ixyr[1] + int(round(self.ir_ratio*ixyr[2])))) + + return image, image_crop, mask, back_mask, pxyr, ixyr, alpha + + def getCodeAndPolarMask(self, image, mask, pxyr, ixyr): + image_polar, mask_polar = self.irisRec.cartToPol_torch(image.convert('L'), mask, pxyr, ixyr) + image_code = self.irisRec.extractCode(image_polar) + return image_code, mask_polar + + def getHDBIFScore(self, code1, code2, mask1, mask2): + score, _ = self.irisRec.matchCodes(code1, code2, mask1, mask2) + return score + + def getEyePreserveDeform(self, image, mask, back_mask, pxyr, ixyr, target_image, target_mask, target_back_mask, target_pxyr, target_ixyr, hal_vis=False): + if hal_vis: + out_image, hal_mask = self.eyepreserve.deform_with_hal_vis(image, mask, back_mask, pxyr, ixyr, target_image, target_mask, target_back_mask, target_pxyr, target_ixyr, self.device) + out_image_np_vis = np.stack((np.array(out_image),)*3, axis=-1) + out_image_vis = Image.fromarray(out_image_np_vis, 'RGB') + return out_image_vis, out_image + else: + return self.eyepreserve.deform(image, mask, pxyr, ixyr, target_image, target_mask, target_pxyr, target_ixyr, self.device) + + def process_for_direct_deform(self): + self.im1, self.im1_crop, self.mask1, self.back_mask1, self.pxyr1, self.ixyr1, self.alpha1 = self.findMaskAndCircles(self.im1) + self.im1_code, self.mask1_polar = self.getCodeAndPolarMask(self.im1, self.mask1, self.pxyr1, self.ixyr1) + + self.im1_resized = self.im1.resize(self.wide_size) + self.im1_crop_resized = self.im1_crop.resize(self.square_size) + + def process_for_direct_deform_DUAL(self): + ## Same pre-processing as Alpha Deformation ## + # Image 1 + self.im1, self.im1_crop, self.mask1, self.back_mask1, self.pxyr1, self.ixyr1, self.alpha1 = self.findMaskAndCircles(self.im1) + self.im1_code, self.mask1_polar = self.getCodeAndPolarMask(self.im1, self.mask1, self.pxyr1, self.ixyr1) + + self.im1_resized = self.im1.resize(self.wide_size) + self.im1_crop_resized = self.im1_crop.resize(self.square_size) + + # Image 2 + self.im2, self.im2_crop, self.mask2, self.back_mask2, self.pxyr2, self.ixyr2, self.alpha2 = self.findMaskAndCircles(self.im2) + self.im2_code, self.mask2_polar = self.getCodeAndPolarMask(self.im2, self.mask2, self.pxyr2, self.ixyr2) + + self.im2_resized = self.im2.resize(self.wide_size) + self.im2_crop_resized = self.im2_crop.resize(self.square_size) + + ## Beginning of DUAL PDM ## + # Image 1 to Image 2 + self.dnet_im1_im2, dnet_im1_im2_novis = self.getEyePreserveDeform(self.im1.convert('L'), self.mask1.copy(), self.back_mask1.copy(), self.pxyr1.copy(), self.ixyr1.copy(), self.im2.convert("L"), self.mask2.copy(), self.back_mask2.copy(), self.pxyr2.copy(), self.ixyr2.copy(), hal_vis=True) + dnet_im1_im2_novis, _, dnet_im1_im2_mask, _, dnet_im1_im2_pxyr, dnet_im1_im2_ixyr, _ = self.findMaskAndCircles(dnet_im1_im2_novis.convert("L")) + + # Image 2 to Image 1 + self.dnet_im2_im1, dnet_im2_im1_novis = self.getEyePreserveDeform(self.im2.convert('L'), self.mask2.copy(), self.back_mask2.copy(), self.pxyr2.copy(), self.ixyr2.copy(), self.im1.convert("L"), self.mask1.copy(), self.back_mask1.copy(), self.pxyr1.copy(), self.ixyr1.copy(), hal_vis=True) + dnet_im2_im1_novis, _, dnet_im2_im1_mask, _, dnet_im2_im1_pxyr, dnet_im2_im1_ixyr, _ = self.findMaskAndCircles(dnet_im2_im1_novis.convert("L")) + + + def process_for_alpha(self, alpha): + dnet_im1_crop, dnet_im1_nocrop, dnet_im1_highlight = self.eyepreserve.deform_with_alpha(self.im1.convert('L'), self.mask1.copy(), self.back_mask1.copy(), self.pxyr1.copy(), self.ixyr1.copy(), alpha, self.device) + self.dnet_im1_alpha_crop[alpha] = dnet_im1_crop.resize(self.square_size) + w,h = self.im1.size + w2,h2 = dnet_im1_nocrop.size + + self.dnet_im1_alpha_nocrop[alpha] = dnet_im1_nocrop.crop((int(self.w_pad), int(self.h_pad), int((w-self.w_pad)), int(h-self.h_pad))) + logging.info(f"Outgoing image {self.converted} has width x height at {alpha} {w2,h2}, should be {w,h}. w_pad and h_pad {self.w_pad, self.h_pad}") + + dnet_im1_iso, _, _ = self.circlemaskfinder.fix_image(dnet_im1_crop) + dnet_mask1_iso, _, dnet_pxyr1_iso, dnet_ixyr1_iso = self.circlemaskfinder.segmentAndCircApprox(dnet_im1_iso.convert("L")) + dnet_orig_im1_crop = self.eyepreserve.deform(dnet_im1_iso, dnet_mask1_iso, dnet_pxyr1_iso, dnet_ixyr1_iso, self.im1.convert('L'), self.mask1.copy(), self.pxyr1.copy(), self.ixyr1.copy(), self.device) + dnet_orig_im1_iso, _, _ = self.circlemaskfinder.fix_image(dnet_orig_im1_crop) + dnet_orig_mask1_iso, _, dnet_orig_pxyr1_iso, dnet_orig_ixyr1_iso = self.circlemaskfinder.segmentAndCircApprox(dnet_orig_im1_iso.convert("L")) + dnet_orig_im1_polar , dnet_orig_mask1_polar = self.irisRec.cartToPol_torch(dnet_orig_im1_iso, dnet_orig_mask1_iso, dnet_orig_pxyr1_iso, dnet_orig_ixyr1_iso) + dnet_orig_im1_code = self.irisRec.extractCode(dnet_orig_im1_polar) + + dnet_im1_score, _ = self.irisRec.matchCodes(dnet_orig_im1_code, self.im1_code, dnet_orig_mask1_polar, self.mask1_polar) + self.dnet_im1_alpha_score[alpha] = round(dnet_im1_score, 4) + + self.apply_conversion(alpha) + + def process_for_alpha_parallel(self, alpha) -> Tuple[bytearray, float]: + try: + logging.info(f"Processing for alpha:{alpha}") + dnet_im1_crop, dnet_im1_nocrop, dnet_im1_highlight = self.eyepreserve.deform_with_alpha(self.im1.convert('L'), self.mask1.copy(), self.back_mask1.copy(), self.pxyr1.copy(), self.ixyr1.copy(), alpha, self.device) + + w,h = self.im1.size + w2,h2 = dnet_im1_nocrop.size + dnet_im1_score = 0 + + logging.info(f"Outgoing image isConverted: {self.converted}, has width x height at alpha:{alpha} {w2,h2}, should be {w,h}. w_pad and h_pad {self.w_pad, self.h_pad} with score {float(round(dnet_im1_score, 4))}") + logging.info(f"Left, Top, Right, Bottom on {w2,h2}: {int(self.w_pad)}, {int(self.h_pad)}, {int((w-self.w_pad))}, {int((h-self.h_pad))}") + + return (self.img_to_bytes(dnet_im1_nocrop.crop((int(self.w_pad), int(self.h_pad), int((w-self.w_pad)), int((h-self.h_pad))))), float(round(dnet_im1_score, 4))) + + except Exception as e: + logging.error(f"Error IN PROCESSING ALPHA {alpha}: {e}") + return None, None + + + def img_to_bytes(self, image): + byte_arr = io.BytesIO() + image.save(byte_arr, "PNG") + return byte_arr.getvalue() + + def apply_conversion(self, alpha): + self.dnet_im1_alpha_nocrop[alpha] = self.img_to_bytes(self.dnet_im1_alpha_nocrop[alpha]) + + +class ActiveMQListener(stomp.ConnectionListener): + def __init__(self, conn, deformer_wrapper): + self.conn = conn + self.deformer_wrapper = deformer_wrapper + logging.info("Initialized ActiveMQListener") + + def on_message(self, frame): + # Record the start time for the entire method + start_time = time.time() + + headers = frame.headers + message = frame.body + logging.info(f"Received message headers: {headers}") + logging.info(f"Raw message type: {type(message)}") + logging.info(f"Raw message length: {len(message)}") + + try: + # Route based on the destination header + destination = normalize_destination(headers.get('destination')) + if destination == DUAL_PDM_REQUEST_QUEUE: + logging.info("Routing to process_dual_pdm...") + self.process_dual_pdm(headers, message) + elif destination == PDM_REQUEST_QUEUE: + logging.info("Routing to process_single_pdm...") + self.process_single_pdm(headers, message) # Call the existing logic for single PDM + else: + logging.warning(f"Unknown destination: {destination}. Ignoring message.") + except Exception as e: + logging.error(f"Unexpected error from Iwp-PDM: {e}") + + # Record the end time for the entire method + end_time = time.time() + + # Calculate the elapsed time for the entire method + elapsed_time = end_time - start_time + logging.info(f"Building Response time: {elapsed_time:.6f} seconds") + + def process_dual_pdm(self, headers, message): + logging.info("--------------- START OF DUAL-PDM ---------------") + logging.info(f"Received Header and Message for Dual PDM: {headers}, {len(message)}") + + probeId = int(headers['probeId']) + candidateId = int(headers['candidateId']) + + logging.info(f"IDs are:{probeId}, {candidateId} and of type: {type(probeId)}, {type(candidateId)}") + + if not isinstance(message, bytes): + raise ValueError("Unexpected message type: Expected bytes") + + # Deserialize the Protobuf message + logging.info("Attempting to parse Protobuf message...") + query = Pdm_pb2.DualPDMComparison() + query.ParseFromString(message) # Deserialize the byte array into a Proto obj + logging.info(f"Succesfully Parsed Protobuf Request Object") + + # Extract fields from the Protobuf object + probe_bytes = query.probe + candidate_bytes = query.candidate + + logging.info(f"Received probe and candidate bytes of length: {len(probe_bytes)}, {len(candidate_bytes)}") + self.deformer_wrapper.load_dual_images(probe_bytes, candidate_bytes) + + # Kick off direct deformation - LIMITED to only projecting image 1 to image 2 (Does not do Alpha deformation) + self.deformer_wrapper.process_for_direct_deform_DUAL() + + # Prepare Proto message + dualComparisonResponse = Pdm_pb2.DualPDMComparison() + dualComparisonResponse.probe = img_to_bytes(self.deformer_wrapper.dnet_im1_im2) + dualComparisonResponse.candidate = img_to_bytes(self.deformer_wrapper.dnet_im2_im1) + + # Serialized object + serialized_message = dualComparisonResponse.SerializeToString() + + # Process the message and send a response + self.conn.send( + destination=DUAL_PDM_RESPONSE_QUEUE, + body=serialized_message, + headers={ + **ARTEMIS_ANYCAST_HEADERS, + 'content-type': 'application/octet-stream', + 'probeId': probeId, + 'candidateId': candidateId, + }) + logging.info(f"Response sent to DUAL PDM RESPONSE queue! Done!") + logging.info("--------------- END OF DUAL-PDM ---------------") + + def process_single_pdm(self, headers, message): + # Ensure the message is in bytes format + if not isinstance(message, bytes): + raise ValueError("Unexpected message type: Expected bytes") + + # Deserialize the Protobuf message + logging.info("Attempting to parse Protobuf message...") + image_service_query = Iwp_pb2.ImageServiceQuery() + image_service_query.ParseFromString(message) # Deserialize the byte array into a Protobuf object + logging.info(f"Succesfully Parsed Protobuf Request Object") + + # Extract fields from the Protobuf object + image_id = image_service_query.imageId + image_bytes = image_service_query.imageBytes + width = image_service_query.width + height = image_service_query.height + + logging.info(f"Received image ID: {image_id}") + logging.info(f"Image dimensions: {width}x{height}") + + logging.info("Performing image deformation") + self.deformer_wrapper.load_images(image_bytes) + logging.info(f"Incoming image has width x height {self.deformer_wrapper.im1.size}") + self.deformer_wrapper.process_for_direct_deform() + + alphas_np_array = np.linspace(0.2, 0.7, 51) + alphas = np.round(alphas_np_array, 2).tolist() + alphas = [0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7] + + alpha_byte_map = {} + scores = [] + + # Record the start time for the for loop + loop_start_time = time.time() + + logging.info("BEGINING PARALLELIZATION...") + with ThreadPoolExecutor() as executor: + try: + results = list(executor.map(self.deformer_wrapper.process_for_alpha_parallel, alphas)) + except Exception as e: + logging.error(f"Error during parallelization: {e}") + return {}, [] + logging.info(f"FINISHED PARALLELIZATION!") + + + for alpha, (byte_arr, score) in zip(alphas, results): + alpha_byte_map[alpha] = byte_arr + scores.append(score) + + # Record the end time for the for loop + loop_end_time = time.time() + + + # Calculate the elapsed time for the for loop + loop_elapsed_time = loop_end_time - loop_start_time + logging.info(f"Parallelization execution time: {loop_elapsed_time:.6f} seconds") + + logging.info("Serializing into Pdm_protobuf obj") + w, h = self.deformer_wrapper.im1.size + serialized_pdm_data = serialize(w, h, alphas, alpha_byte_map, scores, image_id) + logging.info(f"Response object of type {type(serialized_pdm_data)}") + + # Process the message and send a response + self.conn.send( + destination=PDM_RESPONSE_QUEUE, + body=serialized_pdm_data, + headers={ + **ARTEMIS_ANYCAST_HEADERS, + 'content-type': 'application/octet-stream', + }, + ) + logging.info(f"Response sent to response queue! Done!") + + + +def img_to_bytes(image): + byte_arr = io.BytesIO() + image.save(byte_arr, "PNG") + return byte_arr.getvalue() + +def serialize(deformed_width:int, deformed_height:int, alphas:list, mapping:dict, scores:list, image_id:str): + dw = Pdm_pb2.DeformerWrapper() + + deformedImage = Pdm_pb2.DeformedImage() + PNG = Pdm_pb2.DeformedImage.PNG + + # Loop through all given alphas + for i, a in enumerate(alphas): + deformedImage.imageData = mapping[a] + deformedImage.score = scores[i] + deformedImage.imageType = PNG + deformedImage.width = deformed_width + deformedImage.height = deformed_height + + # Set DeformedImage into Proto class + dw.eyePreserveResponse[str(a)].CopyFrom(deformedImage) + dw.imageId = image_id + + # Begin Serialization + return dw.SerializeToString() + + +def main(args): + # Check for CUDA device + if args.cuda: + if torch.cuda.is_available(): + logger.info('Running on CUDA-enabled GPU.') + device = torch.device('cuda') + else: + logger.warning("CUDA specified but not available, running on CPU.") + device = torch.device('cpu') + else: + logger.info('Running on CPU.') + device = torch.device('cpu') + + deformer_wrapper = DeformerWrapper( + [int(size) for size in args.wide_size.split('x')], + [int(size) for size in args.square_size.split('x')], + device + ) + logging.info("Initialized Deformer Wrapper") + + # Connect to the ActiveMQ Artemis STOMP broker. + broker_host = stomp_host() + username, password = artemis_credentials() + connection_options = {"auto_decode": False} + heartbeats = stomp_heartbeats() + if heartbeats: + connection_options["heartbeats"] = heartbeats + + logging.info( + "Connecting to STOMP broker at %s:%s as %s", + broker_host[0], + broker_host[1], + username, + ) + conn = stomp.Connection([broker_host], **connection_options) + listener = ActiveMQListener(conn, deformer_wrapper) + conn.set_listener('', listener) + conn.connect(username, password, wait=True) + conn.subscribe(destination=PDM_REQUEST_QUEUE, id=1, ack='auto', headers=ARTEMIS_ANYCAST_SUBSCRIBE_HEADERS) + conn.subscribe(destination=DUAL_PDM_REQUEST_QUEUE, id=2, ack='auto', headers=ARTEMIS_ANYCAST_SUBSCRIBE_HEADERS) + + # Keep the application running to listen for messages + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + logger.info("Shutting down...") + conn.disconnect() + sys.exit(0) + except Exception as e: + logger.error("Unexpected error occurred:", exc_info=True) + conn.disconnect() + sys.exit(1) + + +def serialize_and_persist(alphas, deformerWrapper: DeformerWrapper, image_id:str): + dw = Pdm_pb2.DeformerWrapper() + deformedWidth, deformedHeight = deformerWrapper.im1.size + + # Loop through all given alphas + for a in range(len(alphas)): + logging.info(f"Serialzing alpha val: {alphas[a]}, type {type(alphas[a])}") + deformedImage = Pdm_pb2.DeformedImage( + imageData=deformerWrapper.dnet_im1_alpha_nocrop[round(alphas[a], 2)], + score=deformerWrapper.dnet_im1_alpha_score[round(alphas[a], 2)], + imageType=Pdm_pb2.DeformedImage.PNG, + width=deformedWidth, + height=deformedHeight + ) + # Set DeformedImage into Proto class + dw.eyePreserveResponse[str(round(alphas[a], 2))].CopyFrom(deformedImage) + dw.imageId = image_id + + # Begin Serialization + serialized_data = dw.SerializeToString() + + return serialized_data + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(prog='Iris Texture Deformer', + description='This tool implements three different methods for iris texture deformation based on change in pupil size: \ + i) Linear (Daugman\'s rubber sheet), ii) Biomechanical and iii) EyePreserve.') + parser.add_argument('--cuda', action='store_true') + parser.add_argument('--window_size', type=str, default="1280x720", help="This parameter pecifies the initial size of the application window.") + parser.add_argument('--wide_size', type=str, default="600x450", help="This parameter specifies the resolution of uncropped/ISO iris images as it is shown on the application window.") + parser.add_argument('--square_size', type=str, default="450x450", help="This parameter specifies the resolution of cropped iris images as it is shown on the application window.") + + args = parser.parse_args() + main(args) diff --git a/pdm/Dockerfile b/pdm/Dockerfile new file mode 100644 index 0000000..c188f1b --- /dev/null +++ b/pdm/Dockerfile @@ -0,0 +1,19 @@ +FROM python:3.10-slim-bookworm + +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + libgl1 libglib2.0-0 libsm6 libxext6 \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY *.py ./ +COPY models ./models +COPY modules ./modules +COPY cfg.yaml . + +ENTRYPOINT ["python", "Comparison_GUI.py"] \ No newline at end of file diff --git a/pdm/EyePreserve.py b/pdm/EyePreserve.py new file mode 100644 index 0000000..9867774 --- /dev/null +++ b/pdm/EyePreserve.py @@ -0,0 +1,789 @@ +import numpy as np +import torch +import torch +from torch import nn +from torchvision.transforms import Compose, ToTensor, Normalize, Resize +from PIL import Image +import cv2 +from kornia.geometry.transform import rotate as tensor_rotate +from io import StringIO +from scipy import io +import math +from math import pi +from torchvision import transforms +import gc + +def create_circular_mask(h, w, center=None, radius=None): + + if center is None: # use the middle of the image + center = (int(w/2), int(h/2)) + if radius is None: # use the smallest distance between the center and image walls + radius = min(center[0], center[1], w-center[0], h-center[1]) + + Y, X = np.ogrid[:h, :w] + dist_from_center = np.sqrt((X - center[0])**2 + (Y-center[1])**2) + + mask = np.where(dist_from_center <= radius, 255, 0) + return mask + +def crop_iris_w_b(img_tensors, mask_tensors, back_mask_tensors, pupil_xyr, iris_xyr, res=256): + cropped_imgs = [] + cropped_masks = [] + cropped_back_masks = [] + pupil_xyr_new = pupil_xyr.clone().detach() + iris_xyr_new = iris_xyr.clone().detach() + + b,c,h,w = img_tensors.shape + + for i in range(img_tensors.shape[0]): + ix = int(round(iris_xyr[i, 0].item())) + iy = int(round(iris_xyr[i, 1].item())) + ir = iris_xyr[i, 2].item() + + ir_ratio = 16/14 + x_min = (ix - int(round(ir_ratio*ir))) + x_max = (ix + int(round(ir_ratio*ir))) + y_min = (iy - int(round(ir_ratio*ir))) + y_max = (iy + int(round(ir_ratio*ir))) + + if x_min >= 0 and x_max < w and y_min >= 0 and y_min < h: + cropped_img = img_tensors[i, :, y_min:y_max, x_min:x_max].unsqueeze(0) + cropped_mask = mask_tensors[i, :, y_min:y_max, x_min:x_max].unsqueeze(0) + cropped_back_mask = back_mask_tensors[i, :, y_min:y_max, x_min:x_max].unsqueeze(0) + else: + if x_min < 0: + if y_min < 0: + cropped_img = img_tensors[i, :, 0:y_max, 0:x_max].unsqueeze(0) + cropped_mask = mask_tensors[i, :, 0:y_max, 0:x_max].unsqueeze(0) + cropped_back_mask = back_mask_tensors[i, :, 0:y_max, 0:x_max].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (-x_min, 0, -y_min, 0), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (-x_min, 0, -y_min, 0), mode='constant', value=0) + cropped_back_mask = nn.functional.pad(cropped_back_mask, (-x_min, 0, -y_min, 0), mode='constant', value=0) + elif y_max >= h: + cropped_img = img_tensors[i, :, y_min:h, 0:x_max].unsqueeze(0) + cropped_mask = mask_tensors[i, :, y_min:h, 0:x_max].unsqueeze(0) + cropped_back_mask = back_mask_tensors[i, :, y_min:h, 0:x_max].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (-x_min, 0, 0, (y_max - h)), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (-x_min, 0, 0, (y_max - h)), mode='constant', value=0) + cropped_back_mask = nn.functional.pad(cropped_back_mask, (-x_min, 0, 0, (y_max - h)), mode='constant', value=0) + else: + cropped_img = img_tensors[i, :, y_min:y_max, 0:x_max].unsqueeze(0) + cropped_mask = mask_tensors[i, :, y_min:y_max, 0:x_max].unsqueeze(0) + cropped_back_mask = back_mask_tensors[i, :, y_min:y_max, 0:x_max].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (-x_min, 0, 0, 0), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (-x_min, 0, 0, 0), mode='constant', value=0) + cropped_back_mask = nn.functional.pad(cropped_back_mask, (-x_min, 0, 0, 0), mode='constant', value=0) + elif x_max >= w: + if y_min < 0: + cropped_img = img_tensors[i, :, 0:y_max, x_min:w].unsqueeze(0) + cropped_mask = mask_tensors[i, :, 0:y_max, x_min:w].unsqueeze(0) + cropped_back_mask = back_mask_tensors[i, :, 0:y_max, x_min:w].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (0, (x_max - w), -y_min, 0), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (0, (x_max - w), -y_min, 0), mode='constant', value=0) + cropped_back_mask = nn.functional.pad(cropped_back_mask, (0, (x_max - w), -y_min, 0), mode='constant', value=0) + elif y_max >= h: + cropped_img = img_tensors[i, :, y_min:h, x_min:w].unsqueeze(0) + cropped_mask = mask_tensors[i, :, y_min:h, x_min:w].unsqueeze(0) + cropped_back_mask = back_mask_tensors[i, :, y_min:h, x_min:w].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (0, (x_max - w), 0, (y_max - h)), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (0, (x_max - w), 0, (y_max - h)), mode='constant', value=0) + cropped_back_mask = nn.functional.pad(cropped_back_mask, (0, (x_max - w), 0, (y_max - h)), mode='constant', value=0) + else: + cropped_img = img_tensors[i, :, y_min:y_max, x_min:w].unsqueeze(0) + cropped_mask = mask_tensors[i, :, y_min:y_max, x_min:w].unsqueeze(0) + cropped_back_mask = back_mask_tensors[i, :, y_min:y_max, x_min:w].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (0, (x_max - w), 0, 0), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (0, (x_max - w), 0, 0), mode='constant', value=0) + cropped_back_mask = nn.functional.pad(cropped_back_mask, (0, (x_max - w), 0, 0), mode='constant', value=0) + else: + if y_min < 0: + cropped_img = img_tensors[i, :, 0:y_max, x_min:x_max].unsqueeze(0) + cropped_mask = mask_tensors[i, :, 0:y_max, x_min:x_max].unsqueeze(0) + cropped_back_mask = back_mask_tensors[i, :, 0:y_max, x_min:x_max].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (0, 0, -y_min, 0), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (0, 0, -y_min, 0), mode='constant', value=0) + cropped_back_mask = nn.functional.pad(cropped_back_mask, (0, 0, -y_min, 0), mode='constant', value=0) + elif y_max >= h: + cropped_img = img_tensors[i, :, y_min:h, x_min:x_max].unsqueeze(0) + cropped_mask = mask_tensors[i, :, y_min:h, x_min:x_max].unsqueeze(0) + cropped_back_mask = back_mask_tensors[i, :, y_min:h, x_min:x_max].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (0, 0, 0, (y_max - h)), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (0, 0, 0, (y_max - h)), mode='constant', value=0) + cropped_back_mask = nn.functional.pad(cropped_back_mask, (0, 0, 0, (y_max - h)), mode='constant', value=0) + + + cropped_img = nn.functional.interpolate(cropped_img, size=(res, res), mode='bilinear') + cropped_mask = nn.functional.interpolate(cropped_mask, size=(res, res), mode='nearest') + cropped_back_mask = nn.functional.interpolate(cropped_back_mask, size=(res, res), mode='nearest') + + mult = res/(ir_ratio*ir * 2) + pupil_xyr_new[i, 0] = (res/2) - mult * (ir - (pupil_xyr[i, 0] - ix + ir)) + pupil_xyr_new[i, 1] = (res/2) - mult * (ir - (pupil_xyr[i, 1] - iy + ir)) + pupil_xyr_new[i, 2] = mult * pupil_xyr[i, 2] + iris_xyr_new[i, 0] = res/2 + iris_xyr_new[i, 1] = res/2 + iris_xyr_new[i, 2] = ((res/2) - (res/16)) + + cropped_imgs.append(cropped_img) + cropped_masks.append(cropped_mask) + cropped_back_masks.append(cropped_back_mask) + cropped_imgs = torch.cat(cropped_imgs, 0) + cropped_masks = torch.cat(cropped_masks, 0) + cropped_back_masks = torch.cat(cropped_back_masks, 0) + return cropped_imgs, cropped_masks, cropped_back_masks, pupil_xyr_new, iris_xyr_new + +def rotate_tensor_batch_w_b(tensors, masks, back_masks, shifts, pupil_xyr, iris_xyr): + rotated_tensors = [] + rotated_masks = [] + rotated_back_masks = [] + pupil_xyr_new = pupil_xyr.clone().detach() + iris_xyr_new = iris_xyr.clone().detach() + for i in range(tensors.shape[0]): + cx = iris_xyr[i, 0].item() + cy = iris_xyr[i, 1].item() + rot_center = torch.tensor([int(round(cx)), int(round(cy))]).reshape(1,2).float() + tensor = tensors[i, :, :, :].clone().detach().unsqueeze(0) + mask = masks[i, :, :, :].clone().detach().unsqueeze(0) + back_mask = back_masks[i, :, :, :].clone().detach().unsqueeze(0) + rot_degree = torch.tensor((shifts[i]/512)*360).float() + rotated_tensor = tensor_rotate(tensor, -rot_degree, center=rot_center, mode='bilinear', padding_mode='border') + rotated_tensors.append(rotated_tensor) + rotated_mask = tensor_rotate(mask, -rot_degree, center=rot_center, mode='nearest', padding_mode='zeros') + rotated_masks.append(rotated_mask) + rotated_back_mask = tensor_rotate(back_mask, -rot_degree, center=rot_center, mode='nearest', padding_mode='zeros') + rotated_back_masks.append(rotated_back_mask) + rot_radian = (shifts[i]/512) * 2 * math.pi + pupil_xyr_new[i, 0] = (pupil_xyr[i, 0] - cx) * math.cos(-rot_radian) + (pupil_xyr[i, 1] - cy) * math.sin(-rot_radian) + cx + pupil_xyr_new[i, 1] = (pupil_xyr[i, 1] - cy) * math.cos(-rot_radian) - (pupil_xyr[i, 0] - cx) * math.sin(-rot_radian) + cy + iris_xyr_new[i, 0] = (iris_xyr[i, 0] - cx) * math.cos(-rot_radian) + (iris_xyr[i, 1] - cy) * math.sin(-rot_radian) + cx + iris_xyr_new[i, 1] = (iris_xyr[i, 1] - cy) * math.cos(-rot_radian) - (iris_xyr[i, 0] - cx) * math.sin(-rot_radian) + cy + rotated_tensors = torch.cat(rotated_tensors, dim=0) + rotated_masks = torch.cat(rotated_masks, dim=0) + rotated_back_masks = torch.cat(rotated_back_masks, dim=0) + return rotated_tensors, rotated_masks, rotated_back_masks, pupil_xyr_new, iris_xyr_new + +def crop_iris(img_tensors, mask_tensors, pupil_xyr, iris_xyr, res=256): + cropped_imgs = [] + cropped_masks = [] + pupil_xyr_new = pupil_xyr.clone().detach() + iris_xyr_new = iris_xyr.clone().detach() + + b,c,h,w = img_tensors.shape + + for i in range(img_tensors.shape[0]): + ix = int(round(iris_xyr[i, 0].item())) + iy = int(round(iris_xyr[i, 1].item())) + ir = iris_xyr[i, 2].item() + + ir_ratio = 16/14 + x_min = (ix - int(round(ir_ratio*ir))) + x_max = (ix + int(round(ir_ratio*ir))) + y_min = (iy - int(round(ir_ratio*ir))) + y_max = (iy + int(round(ir_ratio*ir))) + + if x_min >= 0 and x_max < w and y_min >= 0 and y_min < h: + cropped_img = img_tensors[i, :, y_min:y_max, x_min:x_max].unsqueeze(0) + cropped_mask = mask_tensors[i, :, y_min:y_max, x_min:x_max].unsqueeze(0) + else: + if x_min < 0: + if y_min < 0: + cropped_img = img_tensors[i, :, 0:y_max, 0:x_max].unsqueeze(0) + cropped_mask = mask_tensors[i, :, 0:y_max, 0:x_max].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (-x_min, 0, -y_min, 0), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (-x_min, 0, -y_min, 0), mode='constant', value=0) + elif y_max >= h: + cropped_img = img_tensors[i, :, y_min:h, 0:x_max].unsqueeze(0) + cropped_mask = mask_tensors[i, :, y_min:h, 0:x_max].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (-x_min, 0, 0, (y_max - h)), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (-x_min, 0, 0, (y_max - h)), mode='constant', value=0) + else: + cropped_img = img_tensors[i, :, y_min:y_max, 0:x_max].unsqueeze(0) + cropped_mask = mask_tensors[i, :, y_min:y_max, 0:x_max].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (-x_min, 0, 0, 0), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (-x_min, 0, 0, 0), mode='constant', value=0) + elif x_max >= w: + if y_min < 0: + cropped_img = img_tensors[i, :, 0:y_max, x_min:w].unsqueeze(0) + cropped_mask = mask_tensors[i, :, 0:y_max, x_min:w].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (0, (x_max - w), -y_min, 0), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (0, (x_max - w), -y_min, 0), mode='constant', value=0) + elif y_max >= h: + cropped_img = img_tensors[i, :, y_min:h, x_min:w].unsqueeze(0) + cropped_mask = mask_tensors[i, :, y_min:h, x_min:w].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (0, (x_max - w), 0, (y_max - h)), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (0, (x_max - w), 0, (y_max - h)), mode='constant', value=0) + else: + cropped_img = img_tensors[i, :, y_min:y_max, x_min:w].unsqueeze(0) + cropped_mask = mask_tensors[i, :, y_min:y_max, x_min:w].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (0, (x_max - w), 0, 0), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (0, (x_max - w), 0, 0), mode='constant', value=0) + else: + if y_min < 0: + cropped_img = img_tensors[i, :, 0:y_max, x_min:x_max].unsqueeze(0) + cropped_mask = mask_tensors[i, :, 0:y_max, x_min:x_max].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (0, 0, -y_min, 0), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (0, 0, -y_min, 0), mode='constant', value=0) + elif y_max >= h: + cropped_img = img_tensors[i, :, y_min:h, x_min:x_max].unsqueeze(0) + cropped_mask = mask_tensors[i, :, y_min:h, x_min:x_max].unsqueeze(0) + cropped_img = nn.functional.pad(cropped_img, (0, 0, 0, (y_max - h)), mode='replicate') + cropped_mask = nn.functional.pad(cropped_mask, (0, 0, 0, (y_max - h)), mode='constant', value=0) + + + cropped_img = nn.functional.interpolate(cropped_img, size=(res, res), mode='bilinear') + cropped_mask = nn.functional.interpolate(cropped_mask, size=(res, res), mode='nearest') + + mult = res/(ir_ratio*ir * 2) + pupil_xyr_new[i, 0] = (res/2) - mult * (ir - (pupil_xyr[i, 0] - ix + ir)) + pupil_xyr_new[i, 1] = (res/2) - mult * (ir - (pupil_xyr[i, 1] - iy + ir)) + pupil_xyr_new[i, 2] = mult * pupil_xyr[i, 2] + iris_xyr_new[i, 0] = res/2 + iris_xyr_new[i, 1] = res/2 + iris_xyr_new[i, 2] = ((res/2) - (res/16)) + + cropped_imgs.append(cropped_img) + cropped_masks.append(cropped_mask) + cropped_imgs = torch.cat(cropped_imgs, 0) + cropped_masks = torch.cat(cropped_masks, 0) + return cropped_imgs, cropped_masks, pupil_xyr_new, iris_xyr_new + +def calculate_shift(anchor_code, anchor_mask, inp_code, inp_mask, max_shift=16): + with torch.no_grad(): + scores = [] + anchor_code_binary = torch.where(anchor_code[:, :12, :, :] > 0, True, False) # b x filt_size x 64 x 512 + inp_code_binary = torch.where(inp_code[:, :12, :, :] > 0, True, False) # b x filt_size x 64 x 512 + for shift in range(-max_shift, max_shift+1): + andMasks = torch.logical_and(anchor_mask, torch.roll(inp_mask, shift, -1)) # b x 1 x 64 x 512 + xorCodes = torch.logical_xor(anchor_code_binary, torch.roll(inp_code_binary, shift, -1)) # b x filt_size x 64 x 512 + xorCodesMasked = torch.logical_and(xorCodes, andMasks.repeat(1, xorCodes.shape[1], 1, 1)) # b x filt_size x 64 x 512 + scores.append((torch.sum(xorCodesMasked, dim=(1,2,3)) / (torch.sum(andMasks, dim=(1,2,3)) * 12)).unsqueeze(-1)) # b * (max_shift+1)*2 + scores = torch.cat(scores, 1) # b x (max_shift+1)*2 + scores_index = torch.argmin(scores, dim=1) + shifts = scores_index - max_shift + return shifts.cpu().numpy().tolist() + +def rotate_tensor_batch(tensors, masks, shifts, pupil_xyr, iris_xyr): + rotated_tensors = [] + rotated_masks = [] + pupil_xyr_new = pupil_xyr.clone().detach() + iris_xyr_new = iris_xyr.clone().detach() + for i in range(tensors.shape[0]): + cx = iris_xyr[i, 0].item() + cy = iris_xyr[i, 1].item() + rot_center = torch.tensor([int(round(cx)), int(round(cy))]).reshape(1,2).float() + tensor = tensors[i, :, :, :].clone().detach().unsqueeze(0) + mask = masks[i, :, :, :].clone().detach().unsqueeze(0) + rot_degree = torch.tensor((shifts[i]/512)*360).float() + rotated_tensor = tensor_rotate(tensor, -rot_degree, center=rot_center, mode='bilinear', padding_mode='border') + rotated_tensors.append(rotated_tensor) + rotated_mask = tensor_rotate(mask, -rot_degree, center=rot_center, mode='bilinear', padding_mode='zeros') + rotated_masks.append(rotated_mask) + rot_radian = (shifts[i]/512) * 2 * math.pi + pupil_xyr_new[i, 0] = (pupil_xyr[i, 0] - cx) * math.cos(-rot_radian) + (pupil_xyr[i, 1] - cy) * math.sin(-rot_radian) + cx + pupil_xyr_new[i, 1] = (pupil_xyr[i, 1] - cy) * math.cos(-rot_radian) - (pupil_xyr[i, 0] - cx) * math.sin(-rot_radian) + cy + iris_xyr_new[i, 0] = (iris_xyr[i, 0] - cx) * math.cos(-rot_radian) + (iris_xyr[i, 1] - cy) * math.sin(-rot_radian) + cx + iris_xyr_new[i, 1] = (iris_xyr[i, 1] - cy) * math.cos(-rot_radian) - (iris_xyr[i, 0] - cx) * math.sin(-rot_radian) + cy + rotated_tensors = torch.cat(rotated_tensors, dim=0) + rotated_masks = torch.cat(rotated_masks, dim=0) + return rotated_tensors, rotated_masks, pupil_xyr_new, iris_xyr_new + +class SIFLayerMaskOSIRIS(nn.Module): + def __init__(self, polar_height, polar_width, filter_mat1, filter_mat2, osiris_filters, device, channels = 1): + super().__init__() + with torch.inference_mode(): + self.device = device + self.polar_height = polar_height + self.polar_width = polar_width + self.channels = channels + + self.filter1_size = filter_mat1.shape[0] + self.num_filters1 = filter_mat1.shape[2] + self.filter1 = torch.FloatTensor(filter_mat1).to(self.device) + self.filter_mat1 = filter_mat1 + self.filter1 = torch.moveaxis(self.filter1.unsqueeze(0), 3, 0).detach().requires_grad_(False) + + self.filter2_size = filter_mat2.shape[0] + self.num_filters2 = filter_mat2.shape[2] + self.filter2 = torch.FloatTensor(filter_mat2).to(self.device) + self.filter_mat2 = filter_mat2 + self.filter2 = torch.moveaxis(self.filter2.unsqueeze(0), 3, 0).detach().requires_grad_(False) + + self.np_filters = [] + with open(osiris_filters, 'r') as osirisFile: + string_inp = '' + for line in osirisFile: + if not line.strip() == 'end': + string_inp += line.strip() + '\n' + else: + c = StringIO(string_inp) + self.np_filters.append(np.loadtxt(c)) + string_inp = '' + + self.torch_filters = [] + + self.torch_filters.append(self.filter1) + self.torch_filters.append(self.filter2) + + self.max_filter_size = max(self.filter1.shape[2], self.filter2.shape[2]) + + for np_filter in self.np_filters: + torch_filter = torch.FloatTensor(np_filter).to(self.device) + torch_filter = torch_filter.unsqueeze(0).unsqueeze(0).detach().requires_grad_(False) + self.max_filter_size = max(self.max_filter_size, torch_filter.shape[2]) + self.torch_filters.append(torch_filter) + + print('Max Filter Size:', self.max_filter_size) + self.n_filters = len(self.torch_filters) + + def grid_sample(self, input, grid, interp_mode='bilinear'): + + N, C, H, W = input.shape + gridx = grid[:, :, :, 0] + gridy = grid[:, :, :, 1] + gridx = ((gridx + 1) / 2 * W - 0.5) / (W - 1) * 2 - 1 + gridy = ((gridy + 1) / 2 * H - 0.5) / (H - 1) * 2 - 1 + newgrid = torch.stack([gridx, gridy], dim=-1).requires_grad_(False) + return torch.nn.functional.grid_sample(input, newgrid, mode=interp_mode, padding_mode='zeros') + + def cartToPol(self, image, mask, pupil_xyr, iris_xyr): + with torch.inference_mode(): + batch_size = image.shape[0] + width = image.shape[3] + height = image.shape[2] + + polar_height = self.polar_height + polar_width = self.polar_width + + pupil_xyr = pupil_xyr.clone().detach().requires_grad_(False) + iris_xyr = iris_xyr.clone().detach().requires_grad_(False) + + theta = (2*pi*torch.linspace(0,polar_width-1,polar_width)/polar_width).requires_grad_(False).to(self.device) + + pxCirclePoints = pupil_xyr[:, 0].reshape(-1, 1) + pupil_xyr[:, 2].reshape(-1, 1) @ torch.cos(theta).reshape(1, polar_width) #b x 512 + pyCirclePoints = pupil_xyr[:, 1].reshape(-1, 1) + pupil_xyr[:, 2].reshape(-1, 1) @ torch.sin(theta).reshape(1, polar_width) #b x 512 + + ixCirclePoints = iris_xyr[:, 0].reshape(-1, 1) + iris_xyr[:, 2].reshape(-1, 1) @ torch.cos(theta).reshape(1, polar_width) #b x 512 + iyCirclePoints = iris_xyr[:, 1].reshape(-1, 1) + iris_xyr[:, 2].reshape(-1, 1) @ torch.sin(theta).reshape(1, polar_width) #b x 512 + + radius = (torch.linspace(1,polar_height,polar_height)/polar_height).reshape(-1, 1).requires_grad_(False).to(self.device) #64 x 1 + + pxCoords = torch.matmul((1-radius), pxCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + pyCoords = torch.matmul((1-radius), pyCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + + ixCoords = torch.matmul(radius, ixCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + iyCoords = torch.matmul(radius, iyCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + + x = (pxCoords + ixCoords).float() + x_norm = ((x-1)/(width-1))*2 - 1 #b x 64 x 512 + + y = (pyCoords + iyCoords).float() + y_norm = ((y-1)/(height-1))*2 - 1 #b x 64 x 512 + + grid_sample_mat = torch.cat([x_norm.unsqueeze(-1), y_norm.unsqueeze(-1)], dim=-1).detach().requires_grad_(False) + + if mask is not None: + if torch.is_tensor(mask): + mask_t = mask.clone().detach().to(self.device) + else: + mask_t = torch.tensor(mask).float().to(self.device) + mask_polar = self.grid_sample(mask_t, grid_sample_mat, interp_mode='nearest') + mask_polar = (mask_polar - mask_polar.min()) / mask_polar.max() + mask_polar = torch.where(mask_polar > 0.5, 1.0, 0.0) + else: + mask_polar = None + + image_polar = self.grid_sample(image, grid_sample_mat, interp_mode='bilinear') + return image_polar, mask_polar + + def cartToPolIrisCenter(self, image, mask, pupil_xyr, iris_xyr): + with torch.inference_mode(): + batch_size = image.shape[0] + width = image.shape[3] + height = image.shape[2] + + polar_height = self.polar_height + polar_width = self.polar_width + + pupil_xyr = pupil_xyr.clone().detach().requires_grad_(False) + iris_xyr = iris_xyr.clone().detach().requires_grad_(False) + + theta = (2*pi*torch.linspace(0,polar_width-1,polar_width)/polar_width).requires_grad_(False) + + pxCirclePoints = iris_xyr[:, 0].reshape(-1, 1) + pupil_xyr[:, 2].reshape(-1, 1) @ torch.cos(theta).reshape(1, polar_width) #b x 512 + pyCirclePoints = iris_xyr[:, 1].reshape(-1, 1) + pupil_xyr[:, 2].reshape(-1, 1) @ torch.sin(theta).reshape(1, polar_width) #b x 512 + + ixCirclePoints = iris_xyr[:, 0].reshape(-1, 1) + iris_xyr[:, 2].reshape(-1, 1) @ torch.cos(theta).reshape(1, polar_width) #b x 512 + iyCirclePoints = iris_xyr[:, 1].reshape(-1, 1) + iris_xyr[:, 2].reshape(-1, 1) @ torch.sin(theta).reshape(1, polar_width) #b x 512 + + radius = (torch.linspace(1,polar_height,polar_height)/polar_height).reshape(-1, 1).requires_grad_(False) #64 x 1 + + pxCoords = torch.matmul((1-radius), pxCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + pyCoords = torch.matmul((1-radius), pyCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + + ixCoords = torch.matmul(radius, ixCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + iyCoords = torch.matmul(radius, iyCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + + x = (pxCoords + ixCoords).float() + x_norm = ((x-1)/(width-1))*2 - 1 #b x 64 x 512 + + y = (pyCoords + iyCoords).float() + y_norm = ((y-1)/(height-1))*2 - 1 #b x 64 x 512 + + grid_sample_mat = torch.cat([x_norm.unsqueeze(-1), y_norm.unsqueeze(-1)], dim=-1).detach().requires_grad_(False) + + if mask is not None: + if torch.is_tensor(mask): + mask_t = mask.clone().detach() + else: + mask_t = torch.tensor(mask).float() + mask_polar = self.grid_sample(mask_t, grid_sample_mat, interp_mode='nearest') + mask_polar = (mask_polar - mask_polar.min()) / mask_polar.max() + mask_polar = torch.where(mask_polar > 0.5, 1.0, 0.0) + else: + mask_polar = None + + image_polar = self.grid_sample(image, grid_sample_mat, interp_mode='bilinear') + return image_polar, mask_polar + + def getCodes(self, image_polar): + codes_list = [] + for filter in self.torch_filters: + r1 = int(filter.shape[2]/2) + r2 = int(filter.shape[3]/2) + imgWrap = nn.functional.pad(image_polar, (0, 0, r1, r1), mode='replicate') + imgWrap = nn.functional.pad(imgWrap, (r2, r2, 0, 0), mode='circular') + code = nn.functional.conv2d(imgWrap, filter, stride=1, padding='valid') + codes_list.append(code) + codes = torch.cat(codes_list, dim=1) + return codes + + def getCodesCPU(self, image_polar): + image_polar = image_polar.cpu() + codes_list = [] + for filter in self.torch_filters: + r1 = int(filter.shape[2]/2) + r2 = int(filter.shape[3]/2) + imgWrap = nn.functional.pad(image_polar, (0, 0, r1, r1), mode='replicate') + imgWrap = nn.functional.pad(imgWrap, (r2, r2, 0, 0), mode='circular') + code = nn.functional.conv2d(imgWrap, filter.cpu(), stride=1, padding='valid') + codes_list.append(code) + codes = torch.cat(codes_list, dim=1) + return codes + + def forward(self, image, pupil_xyr, iris_xyr, mask=None): + image_polar, mask_polar = self.cartToPol(image, mask, pupil_xyr, iris_xyr) + codes = self.getCodes(image_polar) + return codes, image_polar, mask_polar + +class EyePreserve(object): + def __init__(self, net_path="./models/0007-val_loss-22.957276336365826+-0.0-val_bit_match-81.10588183031244+-0.0-val_linear_bit_match-75.13033321984577+-0.0.pth", sif_filter_path1="./models/ICAtextureFilters_15x15_7bit.mat", sif_filter_path2="./models/ICAtextureFilters_17x17_5bit.mat", osiris_filters_path="./models/osiris_filters.txt", device=torch.device('cpu')): + self.device = device + self.nets = torch.load(net_path, map_location=self.device, weights_only=False) + self.net = self.nets[0].to(device) + self.net.eval() + del self.nets[1] + gc.collect() + torch.cuda.empty_cache() + self.transform = Compose([ + ToTensor(), + Normalize((0.5, ), (0.5, )) + ]) + filter_mat1 = io.loadmat(sif_filter_path1)['ICAtextureFilters'] + filter_mat2 = io.loadmat(sif_filter_path2)['ICAtextureFilters'] + self.sifLossModel = SIFLayerMaskOSIRIS(polar_height = 64, polar_width = 512, filter_mat1 = filter_mat1, filter_mat2 = filter_mat2, osiris_filters = osiris_filters_path, device=device).to(device) + self.transform = transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.5,), (0.5, )) + ]) + + def find_xy_between(self, r1, r2, r1p, r3p, xp, yp, xc, yc): + r3 = (((r3p - r1p) * (r2 - r1)) / (r2 - r1p)) + r1 + x = ((r3 * (xp - xc)) / r3p) + xc + y = ((r3 * (yp - yc)) / r3p) + yc + return x, y + + def find_xy_less(self, r1, r1p, xp, yp, xc, yc): + x = ((r1 * (xp - xc)) / r1p) + xc + y = ((r1 * (yp - yc)) / r1p) + yc + return x, y + + def create_grid(self, r1, r2, r1p, b, h, w, xc, yc): # batched implementation + Y = torch.arange(0, h).reshape(1, h, 1).repeat(b, 1, w).float().to(self.device) + X = torch.arange(0, w).reshape(1, 1, w).repeat(b, h, 1).float().to(self.device) + xcr = xc.reshape(-1, 1, 1).repeat(1, h, w).float() + ycr = yc.reshape(-1, 1, 1).repeat(1, h, w).float() + + r3p = torch.sqrt(torch.square(X - xcr) + torch.square(Y - ycr)) #(b,h,w) #could be a source of nan + r1r = r1.reshape(-1, 1, 1).repeat(1, h, w) + r2r = r2.reshape(-1, 1, 1).repeat(1, h, w) + r1pr = r1p.reshape(-1, 1, 1).repeat(1, h, w) + + no_change_cond = torch.where(r3p > r2r, 1, 0).unsqueeze(3).repeat(1,1,1,2).to(self.device) + no_change = torch.cat((X.unsqueeze(3), Y.unsqueeze(3)), dim=3) + xy_between_cond = torch.where(torch.logical_and(r3p >= r1pr, r3p <= r2r), 1, 0).unsqueeze(3).repeat(1,1,1,2).to(self.device) + x_between, y_between = self.find_xy_between(r1r, r2r, r1pr, r3p, X, Y, xcr, ycr) + xy_between = torch.cat((x_between.unsqueeze(3), y_between.unsqueeze(3)), dim=3).to(self.device) + + xy_less_cond = torch.where(r3p < r1pr, 1, 0).unsqueeze(3).repeat(1,1,1,2).to(self.device) + x_less, y_less = self.find_xy_less(r1r, r1pr, X, Y, xcr, ycr) + xy_less = torch.cat((x_less.unsqueeze(3), y_less.unsqueeze(3)), dim=3).to(self.device) + + grid = (no_change_cond * no_change + xy_between_cond * xy_between + xy_less_cond * xy_less).float().to(self.device) + gridx = grid[:, :, :, 0] + gridy = grid[:, :, :, 1] + gridx = gridx / (w - 1) + gridx = (gridx - 0.5) * 2 + gridy = gridy / (h - 1) + gridy = (gridy - 0.5) * 2 + norm_grid = torch.stack([gridx, gridy], dim=-1).to(self.device) + return norm_grid + + def linear_deform(self, image, input_pxyr, input_ixyr, alpha, mode='bilinear'): + xc = ((input_pxyr[:, 0] + input_ixyr[:, 0])/2).float().reshape(-1,1).to(self.device) + yc = ((input_pxyr[:, 1] + input_ixyr[:, 1])/2).float().reshape(-1,1).to(self.device) + r1 = input_pxyr[:, 2].float().reshape(-1,1).to(self.device) + r2 = input_ixyr[:, 2].float().reshape(-1,1).to(self.device) + r1p = r2 * torch.tensor(alpha).unsqueeze(0).float().to(self.device) + b, c, h, w = image.shape + grid = self.create_grid(r1, r2, r1p, b, h, w, xc, yc) + deformed_image = torch.nn.functional.grid_sample(image.to(self.device), grid, mode=mode) + return deformed_image, grid + + @torch.inference_mode() + def deform(self, image, image_mask, image_pxyr, image_ixyr, target_image, target_mask, target_pxyr, target_ixyr, device): #Update to remove mean and std and add it later, input processing is wrong here + inp_t = torch.from_numpy(np.float32(image)).float().unsqueeze(0).unsqueeze(0) + + xform_inp_mask = self.transform(image_mask) + xform_inp_mask[xform_inp_mask<0] = -1.0 + xform_inp_mask[xform_inp_mask>=0] = 1.0 + xform_inp_mask = xform_inp_mask.unsqueeze(0) + + image_pxyr = torch.tensor(image_pxyr).unsqueeze(0).float() + image_ixyr = torch.tensor(image_ixyr).unsqueeze(0).float() + + tar_t = torch.from_numpy(np.float32(target_image)).float().unsqueeze(0).unsqueeze(0) + + xform_tar_mask = self.transform(target_mask) + xform_tar_mask[xform_tar_mask<0] = -1.0 + xform_tar_mask[xform_tar_mask>=0] = 1.0 + xform_tar_mask = xform_tar_mask.unsqueeze(0) + + target_pxyr = torch.tensor(target_pxyr).unsqueeze(0).float() + target_ixyr = torch.tensor(target_ixyr).unsqueeze(0).float() + + inp_polar_iris, inp_mask_polar_iris = self.sifLossModel.cartToPolIrisCenter(inp_t, ((xform_inp_mask+1)/2), image_pxyr, image_ixyr) + sif_inp_iris = self.sifLossModel.getCodesCPU(inp_polar_iris) + + tar_polar_unrot, tar_mask_polar_unrot = self.sifLossModel.cartToPolIrisCenter(tar_t, ((xform_tar_mask+1)/2), target_pxyr, target_ixyr) + sif_tar_unrot = self.sifLossModel.getCodesCPU(tar_polar_unrot) + + shift_tar = calculate_shift(sif_inp_iris, inp_mask_polar_iris, sif_tar_unrot, tar_mask_polar_unrot) + + del inp_polar_iris, inp_mask_polar_iris, sif_inp_iris + del tar_polar_unrot, tar_mask_polar_unrot, sif_tar_unrot + + inp_t, xform_inp_mask, image_pxyr_new, image_ixyr_new = crop_iris(inp_t.to(device), xform_inp_mask.to(device), image_pxyr.to(device), image_ixyr.to(device)) + + inp_img_mean = torch.mean(inp_t) + inp_img_std = torch.std(inp_t) + + xform_inp = torch.clamp(torch.nan_to_num((inp_t - inp_img_mean)/inp_img_std, nan=4, posinf=4, neginf=-4), -4, 4) + xform_inp = xform_inp + + tar_t, xform_tar_mask, target_pxyr_new, target_ixyr_new = rotate_tensor_batch(tar_t, xform_tar_mask, shift_tar, target_pxyr, target_ixyr) + tar_t, xform_tar_mask, target_pxyr_new, target_ixyr_new = crop_iris(tar_t.to(device), xform_tar_mask.to(device), target_pxyr_new.to(device), target_ixyr_new.to(device)) + + tar_img_mean = torch.mean(tar_t) + tar_img_std = torch.std(tar_t) + + xform_tar = torch.clamp(torch.nan_to_num((tar_t - tar_img_mean)/tar_img_std, nan=4, posinf=4, neginf=-4), -4, 4) + xform_tar = xform_tar + + target_alpha = (target_pxyr_new[:, 2]/target_ixyr_new[:, 2]).reshape(-1, 1).to(device) + ld_in, _ = self.linear_deform(xform_inp, image_pxyr_new, image_ixyr_new, target_alpha) + ld_in = ld_in.detach().requires_grad_(False).float() + ld_in = torch.clamp(torch.nan_to_num(ld_in, nan=4, posinf=4, neginf=-4), -4, 4) + + out = self.net(torch.cat([xform_inp, ld_in, xform_tar_mask], 1)) + out_norm = ((out * tar_img_std) + tar_img_mean).to(device, non_blocking=True) + out_image_np = torch.clamp(out_norm[0].clone().detach(), 0, 255).cpu().numpy()[0] + out_image = Image.fromarray(np.uint8(out_image_np)) + + tar_mask = np.uint8(torch.where(((xform_tar_mask+1)/2) < 0.5, 0, 255).cpu().numpy()[0][0]) + tar_mask_pil = Image.fromarray(tar_mask, 'L') + + return out_image + + @torch.inference_mode() + def deform_with_hal_vis(self, image, image_mask, image_back_mask, image_pxyr, image_ixyr, target_image, target_mask, target_back_mask, target_pxyr, target_ixyr, device): #Update to remove mean and std and add it later, input processing is wrong here + inp_t = torch.from_numpy(np.float32(image)).float().unsqueeze(0).unsqueeze(0) + + xform_inp_mask = self.transform(image_mask) + xform_inp_mask[xform_inp_mask<0] = -1.0 + xform_inp_mask[xform_inp_mask>=0] = 1.0 + xform_inp_mask = xform_inp_mask.unsqueeze(0) + + inp_back_mask_t = torch.from_numpy(np.float32(image_back_mask)).float().unsqueeze(0).unsqueeze(0) + + image_pxyr = torch.tensor(image_pxyr).unsqueeze(0).float() + image_ixyr = torch.tensor(image_ixyr).unsqueeze(0).float() + + tar_t = torch.from_numpy(np.float32(target_image)).float().unsqueeze(0).unsqueeze(0) + + xform_tar_mask = self.transform(target_mask) + xform_tar_mask[xform_tar_mask<0] = -1.0 + xform_tar_mask[xform_tar_mask>=0] = 1.0 + xform_tar_mask = xform_tar_mask.unsqueeze(0) + + tar_back_mask_t = torch.from_numpy(np.float32(target_back_mask)).float().unsqueeze(0).unsqueeze(0) + + target_pxyr = torch.tensor(target_pxyr).unsqueeze(0).float() + target_ixyr = torch.tensor(target_ixyr).unsqueeze(0).float() + + inp_polar_iris, inp_mask_polar_iris = self.sifLossModel.cartToPolIrisCenter(inp_t, ((xform_inp_mask+1)/2), image_pxyr, image_ixyr) + sif_inp_iris = self.sifLossModel.getCodesCPU(inp_polar_iris) + + tar_polar_unrot, tar_mask_polar_unrot = self.sifLossModel.cartToPolIrisCenter(tar_t, ((xform_tar_mask+1)/2), target_pxyr, target_ixyr) + sif_tar_unrot = self.sifLossModel.getCodesCPU(tar_polar_unrot) + + shift_tar = calculate_shift(sif_inp_iris, inp_mask_polar_iris, sif_tar_unrot, tar_mask_polar_unrot) + + del inp_polar_iris, inp_mask_polar_iris, sif_inp_iris + del tar_polar_unrot, tar_mask_polar_unrot, sif_tar_unrot + + inp_t, xform_inp_mask, inp_back_mask_t, image_pxyr_new, image_ixyr_new = crop_iris_w_b(inp_t.to(device), xform_inp_mask.to(device), inp_back_mask_t.to(device), image_pxyr.to(device), image_ixyr.to(device)) + + inp_img_mean = torch.mean(inp_t) + inp_img_std = torch.std(inp_t) + + xform_inp = torch.clamp(torch.nan_to_num((inp_t - inp_img_mean)/inp_img_std, nan=4, posinf=4, neginf=-4), -4, 4) + xform_inp = xform_inp + + tar_t, xform_tar_mask, tar_back_mask_t, target_pxyr_new, target_ixyr_new = rotate_tensor_batch_w_b(tar_t, xform_tar_mask, tar_back_mask_t, shift_tar, target_pxyr, target_ixyr) + tar_t, xform_tar_mask, tar_back_mask_t, target_pxyr_new, target_ixyr_new = crop_iris_w_b(tar_t.to(device), xform_tar_mask.to(device), tar_back_mask_t.to(device), target_pxyr_new.to(device), target_ixyr_new.to(device)) + + tar_img_mean = torch.mean(tar_t) + tar_img_std = torch.std(tar_t) + + xform_tar = torch.clamp(torch.nan_to_num((tar_t - tar_img_mean)/tar_img_std, nan=4, posinf=4, neginf=-4), -4, 4) + xform_tar = xform_tar + + target_alpha = (target_pxyr_new[:, 2]/target_ixyr_new[:, 2]).reshape(-1, 1).to(device) + ld_in, _ = self.linear_deform(xform_inp, image_pxyr_new, image_ixyr_new, target_alpha) + ld_in = ld_in.detach().requires_grad_(False).float() + ld_in = torch.clamp(torch.nan_to_num(ld_in, nan=4, posinf=4, neginf=-4), -4, 4) + + out = self.net(torch.cat([xform_inp, ld_in, xform_tar_mask], 1)) + out_norm = ((out * tar_img_std) + tar_img_mean).to(device, non_blocking=True) + out_image_np = np.uint8(torch.clamp(out_norm[0].clone().detach(), 0, 255).cpu().numpy()[0]) + + out_image = Image.fromarray(out_image_np, 'L') + + inp_back_mask = np.uint8(inp_back_mask_t.cpu().numpy()[0][0]) + tar_back_mask = np.uint8(tar_back_mask_t.cpu().numpy()[0][0]) + tar_mask = np.uint8(torch.where(((xform_tar_mask+1)/2) < 0.5, 0, 255).cpu().numpy()[0][0]) + hal_mask = cv2.bitwise_and(tar_mask, cv2.bitwise_and(tar_back_mask, cv2.bitwise_not(inp_back_mask))) + + return out_image, hal_mask + + @torch.inference_mode() + def deform_with_alpha(self, image, iris_mask, back_mask, image_pxyr, image_ixyr, target_alpha, device): #Update to remove mean and std and add it later, input processing is wrong here + + image_nocrop = image.copy() + iris_mask_nocrop = iris_mask.copy() + back_mask_nocrop = back_mask.copy() + image_pxyr_nocrop = np.copy(image_pxyr) + image_ixyr_nocrop = np.copy(image_ixyr) + + ir_ratio = 16/14 + border = int(round(ir_ratio*image_ixyr[2])) + image = image.crop((image_ixyr[0] - border, image_ixyr[1] - border, image_ixyr[0] + border, image_ixyr[1] + border)) + iris_mask = iris_mask.crop((image_ixyr[0] - border, image_ixyr[1] - border, image_ixyr[0] + border, image_ixyr[1] + border)) + back_mask = back_mask.crop((image_ixyr[0] - border, image_ixyr[1] - border, image_ixyr[0] + border, image_ixyr[1] + border)) + w, h = image.size + + image_pxyr[0] = float(w)/2.0 + image_pxyr[0] - image_ixyr[0] + image_pxyr[1] = float(h)/2.0 + image_pxyr[1] - image_ixyr[1] + + image_ixyr[0] = float(w)/2.0 + image_ixyr[1] = float(h)/2.0 + + inp_alpha = image_pxyr[2] / image_ixyr[2] + if inp_alpha == target_alpha: + return image, image_nocrop + else: + + inp_t = torch.from_numpy(np.float32(image)).float().unsqueeze(0).unsqueeze(0) + inp_img_mean = torch.mean(inp_t) + inp_img_std = torch.std(inp_t) + + image_pxyr_t = torch.tensor(image_pxyr).unsqueeze(0).float() + image_ixyr_t = torch.tensor(image_ixyr).unsqueeze(0).float() + + xform_inp = torch.clamp(torch.nan_to_num((inp_t - inp_img_mean)/inp_img_std, nan=4, posinf=4, neginf=-4), -4, 4) + xform_inp = xform_inp + + ld_in, _ = self.linear_deform(xform_inp, image_pxyr_t, image_ixyr_t, target_alpha) + ld_in = ld_in.detach().requires_grad_(False).float() + ld_in = torch.clamp(torch.nan_to_num(ld_in, nan=4, posinf=4, neginf=-4), -4, 4) + + xform_inp = Resize(256)(xform_inp).to(device) + ld_in = Resize(256)(ld_in).to(device) + + if inp_alpha < target_alpha: + w, h = iris_mask.size + pupil_mask = create_circular_mask(h, w, center=(image_pxyr[0], image_pxyr[1]), radius=(target_alpha * image_ixyr[2])) + target_mask_np = np.uint8(np.array(iris_mask) - pupil_mask) + else: + w, h = iris_mask.size + pupil_fill_mask = create_circular_mask(h, w, center=(image_pxyr[0], image_pxyr[1]), radius=(image_pxyr[2] + ((image_ixyr[2] - image_pxyr[2])/4))) + filled_iris_mask = cv2.bitwise_and(cv2.bitwise_or(np.uint8(iris_mask), np.uint8(pupil_fill_mask)), np.uint8(back_mask)) + pupil_mask = create_circular_mask(h, w, center=(image_pxyr[0], image_pxyr[1]), radius=(target_alpha * image_ixyr[2])) + target_mask_np = np.uint8(np.array(filled_iris_mask) - pupil_mask) + + target_mask = Image.fromarray(target_mask_np, "L").resize((256, 256)) + #target_mask.save('target_mask_' + str(target_alpha) + '.png') + xform_tar_mask = self.transform(target_mask) + xform_tar_mask[xform_tar_mask<0] = -1.0 + xform_tar_mask[xform_tar_mask>=0] = 1.0 + xform_tar_mask = xform_tar_mask.unsqueeze(0).to(device) + #print(xform_inp.shape, ld_in.shape, xform_tar_mask.shape) + + out = self.net(torch.cat([xform_inp, ld_in, xform_tar_mask], 1)) + out_norm = ((out * inp_img_std) + inp_img_mean).to(device, non_blocking=True) + out_image_np = torch.clamp(out_norm[0].clone().detach(), 0, 255).cpu().numpy()[0] + out_image = Image.fromarray(np.uint8(out_image_np)) + + out_image_crop = out_image.copy() + out_image = out_image.resize((round(ir_ratio * image_ixyr[2] * 2), round(ir_ratio * image_ixyr[2] * 2))) + out_image_np = (np.array(out_image).astype(np.float32) - np.mean(np.array(out_image)))/np.std(np.array(out_image)) + iso_img = np.float32(image_nocrop.copy()) + iso_img_crop = iso_img[int(round(image_ixyr_nocrop[1] - ir_ratio * image_ixyr[2])) : int(round(image_ixyr_nocrop[1] + ir_ratio * image_ixyr[2])), int(round(image_ixyr_nocrop[0] - ir_ratio * image_ixyr[2])) : int(round(image_ixyr_nocrop[0] + ir_ratio * image_ixyr[2]))] + iso_img_mean = np.mean(iso_img_crop) + iso_img_std = np.std(iso_img_crop) + out_image_np = (out_image_np * iso_img_std) + iso_img_mean + w, h = iris_mask_nocrop.size + pupil_fill_mask_nocrop = create_circular_mask(h, w, center=(image_pxyr_nocrop[0], image_pxyr_nocrop[1]), radius=(image_pxyr_nocrop[2] + ((image_ixyr_nocrop[2] - image_pxyr_nocrop[2])/4))) + filled_iris_mask_nocrop = cv2.bitwise_and(cv2.bitwise_or(np.uint8(iris_mask_nocrop), np.uint8(pupil_fill_mask_nocrop)), np.uint8(back_mask_nocrop)) + inp_replace_mask = np.where(filled_iris_mask_nocrop >= 127.5, 1.0, 0.0) + inp_replace_mask_inv = np.where(inp_replace_mask > 0.5, 0.0, 1.0) + + iris_img = np.zeros(iso_img.shape).astype(np.float32) + up = int(round(image_ixyr_nocrop[1] - out_image_np.shape[0] / 2.0)) + down = out_image_np.shape[0] + up + left = int(round(image_ixyr_nocrop[0] - out_image_np.shape[0] / 2.0)) + right = out_image_np.shape[1] + left + iris_img[up:down, left:right] = out_image_np + iso_img = iso_img * inp_replace_mask_inv + iris_img * inp_replace_mask + iso_img_pil = Image.fromarray(np.uint8(np.clip(iso_img, 0, 255)), "L") + out_image = iso_img_pil + + kernel = np.ones((3, 3), np.uint8) + iris_mask = np.uint8(np.where(inp_replace_mask > 0.5, 255, 0)) + inp_replace_mask_erode = cv2.erode(iris_mask, kernel, iterations=1) + inp_replace_mask_dilate = cv2.dilate(iris_mask, kernel, iterations=1) + highlight_mask = inp_replace_mask_dilate - inp_replace_mask_erode + + return out_image_crop, out_image, highlight_mask + + \ No newline at end of file diff --git a/pdm/Iwp_pb2.py b/pdm/Iwp_pb2.py new file mode 100644 index 0000000..82b4f40 --- /dev/null +++ b/pdm/Iwp_pb2.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: Iwp.proto +# Protobuf Python Version: 7.35.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 7, + 35, + 1, + '', + 'Iwp.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tIwp.proto\x12\x15org.mitre.iwp.buffers\"\x86\x02\n\x11ImageServiceQuery\x12\x0f\n\x07imageId\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\x12\x12\n\nimageBytes\x18\x03 \x01(\x0c\x12\x45\n\timageType\x18\x04 \x01(\x0e\x32\x32.org.mitre.iwp.buffers.ImageServiceQuery.ImageType\x12\r\n\x05width\x18\x05 \x01(\x05\x12\x0e\n\x06height\x18\x06 \x01(\x05\"X\n\tImageType\x12\x07\n\x03PNG\x10\x00\x12\x07\n\x03JPG\x10\x01\x12\x08\n\x04JPEG\x10\x02\x12\x07\n\x03JP2\x10\x03\x12\x07\n\x03\x42MP\x10\x04\x12\x07\n\x03TIF\x10\x05\x12\x08\n\x04TIFF\x10\x06\x12\n\n\x06PIXMAP\x10\x08\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'Iwp_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_IMAGESERVICEQUERY']._serialized_start=37 + _globals['_IMAGESERVICEQUERY']._serialized_end=299 + _globals['_IMAGESERVICEQUERY_IMAGETYPE']._serialized_start=211 + _globals['_IMAGESERVICEQUERY_IMAGETYPE']._serialized_end=299 +# @@protoc_insertion_point(module_scope) diff --git a/pdm/LinearDeformer.py b/pdm/LinearDeformer.py new file mode 100644 index 0000000..b62c166 --- /dev/null +++ b/pdm/LinearDeformer.py @@ -0,0 +1,145 @@ +import torch +from torch.nn.functional import grid_sample +from torchvision.transforms import ToTensor +import numpy as np +import torch +import torch.nn as nn +from PIL import Image +import math + +class conv(nn.Module): + def __init__(self, in_channels=512, out_n = 6): + super().__init__() + self.conv = nn.Conv2d(in_channels, out_n, kernel_size=1, stride=1, padding='same') + def forward(self, x): + x = self.conv(x) + return x + +class fclayer(nn.Module): + def __init__(self, in_h = 8, in_w = 10, out_n = 6): + super().__init__() + self.in_h = in_h + self.in_w = in_w + self.out_n = out_n + self.fc_list = [] + for i in range(out_n): + self.fc_list.append(nn.Linear(in_h*in_w, 1)) + self.fc_list = nn.ModuleList(self.fc_list) + def forward(self, x): + x = x.reshape(-1, 6, self.in_h, self.in_w) + outs = [] + for i in range(self.out_n): + outs.append(self.fc_list[i](x[:, i, :, :].reshape(-1, self.in_h*self.in_w))) + out = torch.cat(outs, 1) + return out + +class LinearDeformer(object): + def __init__(self, device=torch.device('cpu')): + self.device = device + + def quadSolve(self, A, B, C): + return ((-1 * B.float() - torch.sqrt(B.float() * B.float() - 4 * A.float() * C.float())) / (2 * A.float())) + + def getAngle(self, y, x): + theta = torch.atan2(y, x) + beta = theta + 2 * math.pi + return torch.where(beta < 2 * math.pi, beta, beta - 2 * math.pi) + + def find_xy_less(self, r1, r1p, xp, yp, xc, yc): + x = ((r1 * (xp - xc)) / r1p) + xc + y = ((r1 * (yp - yc)) / r1p) + yc + return x, y + + def do_linear_deform(self, image, mask, pupil_xyr, iris_xyr, target_alpha, width, height): + with torch.no_grad(): + pupil_xyr = torch.tensor(pupil_xyr).float().to(self.device) + iris_xyr = torch.tensor(iris_xyr).float().to(self.device) + + polar_height = round(math.sqrt((pupil_xyr[0].item() - iris_xyr[0].item()) ** 2 + (pupil_xyr[1].item() - iris_xyr[1].item()) ** 2) + (iris_xyr[2].item() - pupil_xyr[2].item())) + polar_width = round(2 * math.pi * iris_xyr[2].item()) + + theta = (2*math.pi*torch.linspace(0,polar_width,polar_width)/polar_width).to(self.device) + pxCirclePoints = pupil_xyr[0] + pupil_xyr[2] * torch.cos(theta).to(self.device) # 512 + pyCirclePoints = pupil_xyr[1] + pupil_xyr[2] * torch.sin(theta).to(self.device) # 512 + + ixCirclePoints = iris_xyr[0] + iris_xyr[2] * torch.cos(theta).to(self.device) # 512 + iyCirclePoints = iris_xyr[1] + iris_xyr[2] * torch.sin(theta).to(self.device) # 512 + + radius = (torch.linspace(1,polar_height,polar_height)/polar_height).reshape(-1, 1).to(self.device) #64 x 1 + + pxCoords = torch.matmul((1.0-radius), pxCirclePoints.reshape(1, polar_width)) # 64 x 512 + pyCoords = torch.matmul((1.0-radius), pyCirclePoints.reshape(1, polar_width)) # 64 x 512 + + ixCoords = torch.matmul(radius, ixCirclePoints.reshape(1, polar_width)) # 64 x 512 + iyCoords = torch.matmul(radius, iyCirclePoints.reshape(1, polar_width)) # 64 x 512 + + grid_x_cartToPol = torch.clamp(pxCoords + ixCoords, 0, width-1).float().to(self.device) # 1 x 1 x 64 x 512 + grid_y_cartToPol = torch.clamp(pyCoords + iyCoords, 0, height-1).float().to(self.device) # 1 x 1 x 64 x 512 + + Y = torch.arange(0, height).reshape(height, 1).repeat(1, width).float().to(self.device) + X = torch.arange(0, width).reshape(1, width).repeat(height, 1).float().to(self.device) + + xcpr = torch.tensor(pupil_xyr[0].item()).reshape(1, 1).repeat(height, width).to(self.device) + ycpr = torch.tensor(pupil_xyr[1].item()).reshape(1, 1).repeat(height, width).to(self.device) + + dists_p = torch.sqrt((X - xcpr) ** 2 + (Y - ycpr) ** 2).to(self.device) + + xcir = torch.tensor(iris_xyr[0].item()).reshape(1, 1).repeat(height, width).to(self.device) + ycir = torch.tensor(iris_xyr[1].item()).reshape(1, 1).repeat(height, width).to(self.device) + + dists_i = torch.sqrt((X - xcir) ** 2 + (Y - ycir) ** 2).to(self.device) + + xy_between_cond = torch.where(torch.logical_and(dists_p > target_alpha * iris_xyr[2].item(), dists_i < int(iris_xyr[2].item())), 1.0, 0.0).to(self.device) + + a = (pupil_xyr[0].item() - iris_xyr[0].item()) ** 2 + (pupil_xyr[1].item() - iris_xyr[1].item()) ** 2 - (target_alpha * iris_xyr[2].item() - iris_xyr[2].item()) ** 2 + b_part = 2 * (target_alpha * iris_xyr[2].item()) * (iris_xyr[2].item() - target_alpha * iris_xyr[2].item()) + c_part = (target_alpha * iris_xyr[2].item()) ** 2 + + A = torch.tensor(a).reshape(1, 1).repeat(height, width).to(self.device) + B = 2 * (X - pupil_xyr[0].item()) * (pupil_xyr[0].item() - iris_xyr[0].item()) + 2 * (Y - pupil_xyr[1].item()) * (pupil_xyr[1].item() - iris_xyr[1].item()) - b_part + C = torch.pow((X - pupil_xyr[0].item()), 2) + torch.pow((Y - pupil_xyr[1].item()), 2) - c_part + r = self.quadSolve(A, B, C).real.float() + X_c = r * float(iris_xyr[0].item()) + (1.0 - r) * float(pupil_xyr[0].item()) + Y_c = r * float(iris_xyr[1].item()) + (1.0 - r) * float(pupil_xyr[1].item()) + grid_x_polToCart = (self.getAngle(Y - Y_c, X - X_c) / (2*math.pi)).float() * (polar_width-1) * xy_between_cond + grid_y_polToCart = r * (polar_height-1) * xy_between_cond + + grid_sample_mat_polToCart = torch.cat([grid_x_polToCart.unsqueeze(-1), grid_y_polToCart.unsqueeze(-1)], dim=-1).unsqueeze(0).to(self.device) + grid_sample_mat_cartToPol = torch.cat([grid_x_cartToPol.unsqueeze(-1), grid_y_cartToPol.unsqueeze(-1)], dim=-1).unsqueeze(0).to(self.device) + + image_polar = self.deform(image, width, height, grid_sample_mat_cartToPol, interp_mode='bicubic') + image_deformed = self.deform(image_polar, polar_width, polar_height, grid_sample_mat_polToCart, interp_mode='bicubic') + + r1r = torch.tensor(pupil_xyr[2].item()).reshape(-1, 1, 1).repeat(1, height, width).to(self.device) + r1pr = torch.tensor(target_alpha * iris_xyr[2].item()).reshape(-1, 1, 1).repeat(1, height, width).to(self.device) + + x_less, y_less = self.find_xy_less(r1r, r1pr, X, Y, xcpr, ycpr) + xy_less = torch.cat((x_less.unsqueeze(3), y_less.unsqueeze(3)), dim=3).float() + + mask_deformed = self.deform(mask.float(), width, height, xy_less, interp_mode='bicubic') + pupil_mask_deformed = torch.where(dists_p < 1.1 * target_alpha * iris_xyr[2].item(), mask_deformed, torch.ones(mask_deformed.shape).float().to(self.device)) + + image_deformed = torch.where(dists_i >= int(iris_xyr[2].item())-1, image, image_deformed) * pupil_mask_deformed + + return image_deformed + + def deform(self, input, W, H, grid, interp_mode): #helper function + with torch.no_grad(): + gridx = grid[:, :, :, 0] + gridy = grid[:, :, :, 1] + gridx = gridx / (W - 1) + gridx = (gridx - 0.5) * 2 + gridy = gridy / (H - 1) + gridy = (gridy - 0.5) * 2 + newgrid = torch.stack([gridx, gridy], dim=-1) + return grid_sample(input, newgrid, mode=interp_mode, align_corners=True) + + def linear_deform(self, image, mask, pupil_xyr, iris_xyr, alpha): + with torch.no_grad(): + image = ToTensor()(image).unsqueeze(0).to(self.device) * 255 + width = image.shape[3] + height = image.shape[2] + mask_t = ToTensor()(mask).unsqueeze(0).to(self.device) + deformed_image = self.do_linear_deform(image, mask_t, pupil_xyr, iris_xyr, alpha, width, height) + deformed_image = torch.clamp(torch.round(deformed_image), min=0, max=255) + return Image.fromarray(deformed_image[0][0].cpu().numpy().astype(np.uint8), 'L') \ No newline at end of file diff --git a/pdm/Pdm_pb2.py b/pdm/Pdm_pb2.py new file mode 100644 index 0000000..0cb669c --- /dev/null +++ b/pdm/Pdm_pb2.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: Pdm.proto +# Protobuf Python Version: 7.35.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 7, + 35, + 1, + '', + 'Pdm.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tPdm.proto\x12\x15org.mitre.iwp.buffers\"5\n\x11\x44ualPDMComparison\x12\r\n\x05probe\x18\x01 \x01(\x0c\x12\x11\n\tcandidate\x18\x02 \x01(\x0c\"\xe2\x01\n\x0f\x44\x65\x66ormerWrapper\x12\x0f\n\x07imageId\x18\x01 \x01(\t\x12\\\n\x13\x65yePreserveResponse\x18\x02 \x03(\x0b\x32?.org.mitre.iwp.buffers.DeformerWrapper.EyePreserveResponseEntry\x1a`\n\x18\x45yePreserveResponseEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x33\n\x05value\x18\x02 \x01(\x0b\x32$.org.mitre.iwp.buffers.DeformedImage:\x02\x38\x01\"\xed\x01\n\rDeformedImage\x12\x11\n\timageData\x18\x01 \x01(\x0c\x12\r\n\x05score\x18\x02 \x01(\x02\x12\x41\n\timageType\x18\x03 \x01(\x0e\x32..org.mitre.iwp.buffers.DeformedImage.ImageType\x12\r\n\x05width\x18\x04 \x01(\x05\x12\x0e\n\x06height\x18\x05 \x01(\x05\"X\n\tImageType\x12\x07\n\x03PNG\x10\x00\x12\x07\n\x03JPG\x10\x01\x12\x08\n\x04JPEG\x10\x02\x12\x07\n\x03JP2\x10\x03\x12\x07\n\x03\x42MP\x10\x04\x12\x07\n\x03TIF\x10\x05\x12\x08\n\x04TIFF\x10\x06\x12\n\n\x06PIXMAP\x10\x07\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'Pdm_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_DEFORMERWRAPPER_EYEPRESERVERESPONSEENTRY']._loaded_options = None + _globals['_DEFORMERWRAPPER_EYEPRESERVERESPONSEENTRY']._serialized_options = b'8\001' + _globals['_DUALPDMCOMPARISON']._serialized_start=36 + _globals['_DUALPDMCOMPARISON']._serialized_end=89 + _globals['_DEFORMERWRAPPER']._serialized_start=92 + _globals['_DEFORMERWRAPPER']._serialized_end=318 + _globals['_DEFORMERWRAPPER_EYEPRESERVERESPONSEENTRY']._serialized_start=222 + _globals['_DEFORMERWRAPPER_EYEPRESERVERESPONSEENTRY']._serialized_end=318 + _globals['_DEFORMEDIMAGE']._serialized_start=321 + _globals['_DEFORMEDIMAGE']._serialized_end=558 + _globals['_DEFORMEDIMAGE_IMAGETYPE']._serialized_start=470 + _globals['_DEFORMEDIMAGE_IMAGETYPE']._serialized_end=558 +# @@protoc_insertion_point(module_scope) diff --git a/pdm/README.md b/pdm/README.md new file mode 100644 index 0000000..8dbdda2 --- /dev/null +++ b/pdm/README.md @@ -0,0 +1,83 @@ +> +> NOTICE +> +> This software (or technical data) was produced for the U. S. Government and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV (May 2014) - Alternative IV (Dec 2007). +> +> (c) 2024 The MITRE Corporation. All Rights Reserved. +> +# Pupil Dilation Mode Component + +The Pupil Dilation Mode (PDM) component is an Iris Analysis Toolkit (IAT) service that changes the pupil-to-iris ratio by dilating the pupil. + +## Starting the Service +This service can be started in two ways: +1. (Recommended) [Docker Image via the IAT](#docker-and-iat) +2. [From a Script](#from-a-script) + +If you make changes to the scripts or models and want to create a new Docker image, see [Rebuild Docker Image](#rebuild-docker-image). + +### Docker and IAT +This is the recommended way. +To start the service through docker and run it with the IAT interface, please follow the [Quickstart for Users](../README.md#quickstart-for-users) instructions. + +### From a Script + +#### Prerequisites + +- An ActiveMQ Artemis-compatible broker reachable over STOMP. + +- The PDM model files in `models/`. To obtain these models, run the following command: + ```sh + ./download-models.sh + ``` + + *NOTE: If the version for the PDM Models changed, the `VERSION_TAG` variable will need to be updated to the correct version. + +- A Python 3.10 environment with the dependencies in `requirements.txt`. To create an active environment with those dependencies, run the following commands: + ```sh + python3.10 -m venv + source /bin/activate + pip install -r requirements.txt + ``` +- A local environment file containing the Artemis credentials. For security reasons, only a `.env.template` file is included with all the variables that need to be filled out. To run the application via docker or locally, please follow these steps/commands: + 1. Make a new file in the `pdm` subdirectory called `.env` from the `.env.template` file by running: + ```sh + cp .env.template .env + ``` + 2. Fill in the Artemis variables in the new `.env` file with a username and password of your choosing. + 3. Change the STOMP variables if needed. + + +### Starting the Script +Once the [prerequisites](#prerequisites) are complete, start the ActiveMQ Artemis broker and run +the following commands from the `pdm` directory: + +```sh +set -a +. ./.env +set +a +python3 Comparison_GUI.py +``` + +To run with a CUDA-capable GPU, append `--cuda` to the final command. + +The service is now running and waits for messages from `iris.pdm.request` and +`iris.dual.pdm.request`, then produces responses on `iris.pdm.response` and +`iris.dual.pdm.response`. Press `Ctrl-C` to stop the service. + +*NOTE: This option is not recommended since a client must publish an `ImageServiceQuery` message to ActiveMQ on `iris.pdm.request` and consume the resulting `DeformerWrapper` message from `iris.pdm.response`. For dual-image processing, the client must publish and consume `DualPDMComparison` messages on `iris.dual.pdm.request` and `iris.dual.pdm.response`, respectively. A complete client script is not currently provided. + +## Rebuild Docker Image +*Before building the Docker image, download the models.* Follow the [model-download](#download-models) instructions. +To build a new Docker image, run this command from the `pdm/` subdirectory: + ```sh + docker build --platform linux/amd64 -t new_pdm_image:YOUR_TAG_HERE \ + -f Dockerfile . + ``` + +## Run Smoke Tests +This project includes a `test.py` script with two smoke tests that send images to the single- and dual-PDM services. Before running the tests, you must: +1. Set the `STOMP_HOST` variable in the `.env` file described in [Prerequisites](#from-a-script). +2. Ensure that the ActiveMQ and PDM services are running. + - You can start the containers by running `docker compose up` in the `../docker/` directory. +3. **OPTIONAL** You might want to replace the `Image.png` file under the `resources/` directory. Just ensure that you keep the name the same or change the name in the `test.py` file. There are 3 locations that would need to be changed in that file. NOTE: The image must be in PNG format. diff --git a/pdm/cfg.yaml b/pdm/cfg.yaml new file mode 100644 index 0000000..cc18f77 --- /dev/null +++ b/pdm/cfg.yaml @@ -0,0 +1,37 @@ +####################################################### +# Segmentation settings +####################################################### +ccnet_model_path: "./models/CCNet_epoch_260_NIRRGBmixed_adam.pth" +mask_model_path: "./models/nestedsharedatrousresunet-217-0.027828-maskIoU-0.938739.pth" +circle_model_path: "./models/convnext_tiny-1076-0.030622-maskIoU-0.938355.pth" +cuda: False +iris_hough_param1: 30 +iris_hough_param2: 5 +iris_hough_margin: 32 +pupil_hough_param1: 30 +pupil_hough_param2: 5 +pupil_hough_minimum: 12 +pupil_iris_max_ratio: 0.7 +max_pupil_iris_shift: 25 + +####################################################### +# Iris coding +####################################################### +polar_height: 64 +polar_width: 512 +recog_filter_size: 17 +recog_num_filters: 5 +recog_max_shift: 16 +recog_bsif_dir: "./models/" +score_normalization: False +threshold_frac_avg_bits: 0 +use_multiprocessing_jitter: True +num_workers: 8 +####################################################### +# Matching visualization +####################################################### +vis_mode: "heat_map" +# heat_map: +# single: annotates only "strong similar regions", that is regions for which % of agreeing bits +# across all filters for a given pixel is larger than "vis_min_agreed_bits" +vis_min_agreed_bits: 50 # used only if vis_mode = single diff --git a/pdm/download-models.sh b/pdm/download-models.sh new file mode 100755 index 0000000..ce34bbf --- /dev/null +++ b/pdm/download-models.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +set -eu + +VERSION_TAG="pdm-models-v1.0.0" +RELEASE_URL="https://github.com/mitre/iat/releases/download/${VERSION_TAG}" +ARCHIVE="${VERSION_TAG}.tar.gz" +CHECKSUM="${VERSION_TAG}.sha256" + +cleanup() { + rm -f "$ARCHIVE" "$CHECKSUM" +} + +trap cleanup EXIT + +curl -fL --retry 3 -o "$ARCHIVE" "$RELEASE_URL/$ARCHIVE" +curl -fL --retry 3 -o "$CHECKSUM" "$RELEASE_URL/$CHECKSUM" + +if command -v sha256sum >/dev/null 2>&1; then + sha256sum -c "$CHECKSUM" +else + shasum -a 256 -c "$CHECKSUM" +fi + +tar -xzf "$ARCHIVE" diff --git a/pdm/modules/irisRecognition.py b/pdm/modules/irisRecognition.py new file mode 100644 index 0000000..19824d4 --- /dev/null +++ b/pdm/modules/irisRecognition.py @@ -0,0 +1,776 @@ +import numpy as np +import cv2 +import torch +import torch.nn as nn +from torch.autograd import Variable +from torchvision import models +from torchvision.transforms import Compose, ToTensor, Normalize +from PIL import Image +import math +from math import pi +from torchvision import models +from modules.network import * +import multiprocessing + +def matchCodesGlobal(irisRecObj, code1, code2, mask1, mask2): #global function required for multiprocessing + r = int(np.floor(irisRecObj.filter_size / 2)) + # Cutting off mask to (64-filter_size+1) x 512 and binarizing it. + mask1_binary = np.where(mask1[r:-r, :] > 127, True, False) + mask2_binary = np.where(mask2[r:-r, :] > 127, True, False) + if (np.sum(mask1_binary) <= irisRecObj.threshold_frac_avg_bits * irisRecObj.avg_num_bits) or (np.sum(mask2_binary) <= irisRecObj.threshold_frac_avg_bits * irisRecObj.avg_num_bits): + return -1.0 + scoreC = [] + for xshift in range(-irisRecObj.max_shift, irisRecObj.max_shift+1): + andMasks = np.logical_and(mask1_binary, np.roll(mask2_binary, xshift, axis=1)) + if np.sum(andMasks) == 0: + scoreC.append(float('inf')) + else: + xorCodes = np.logical_xor(code1, np.roll(code2, xshift, axis=2)) + xorCodesMasked = np.logical_and(xorCodes, np.tile(np.expand_dims(andMasks,axis=0), (irisRecObj.num_filters, 1, 1))) + scoreC.append(np.sum(xorCodesMasked) / (np.sum(andMasks) * irisRecObj.num_filters)) + if irisRecObj.score_norm: + scoreC[-1] = 0.5 - (0.5 - scoreC[-1]) * math.sqrt( np.sum(andMasks) / irisRecObj.avg_num_bits ) + scoreC_best = np.min(np.array(scoreC)) + if scoreC_best == float('inf'): + return -1.0 + return scoreC_best + +class irisRecognition(object): + def __init__(self, cfg, use_hough = False): + # cParsing the config file + self.jitter = 1 + self.use_hough = use_hough + self.polar_height = cfg["polar_height"] + self.polar_width = cfg["polar_width"] + self.filter_size = cfg["recog_filter_size"] + self.num_filters = cfg["recog_num_filters"] + self.max_shift = cfg["recog_max_shift"] + self.cuda = cfg["cuda"] + self.score_norm = cfg["score_normalization"] + self.threshold_frac_avg_bits = cfg["threshold_frac_avg_bits"] + if self.cuda: + self.device = torch.device('cuda') + else: + self.device = torch.device('cpu') + self.mask_model_path = cfg["mask_model_path"] + self.circle_model_path = cfg["circle_model_path"] + self.ccnet_model_path = cfg["ccnet_model_path"] + mat_file_path = cfg["recog_bsif_dir"]+'ICAtextureFilters_{0}x{1}_{2}bit.pt'.format(self.filter_size, self.filter_size, self.num_filters) + with torch.no_grad(): + filter_mat = torch.jit.load(mat_file_path, torch.device('cpu')).ICAtextureFilters.detach().numpy() + self.filter_size = filter_mat.shape[0] + self.num_filters = filter_mat.shape[2] + with torch.no_grad(): + self.torch_filter = torch.FloatTensor(filter_mat).to(self.device) + self.torch_filter = torch.moveaxis(self.torch_filter.unsqueeze(0), 3, 0).detach().requires_grad_(False) + if self.use_hough: + self.iris_hough_param1 = cfg["iris_hough_param1"] + self.iris_hough_param2 = cfg["iris_hough_param2"] + self.iris_hough_margin = cfg["iris_hough_margin"] + self.pupil_hough_param1 = cfg["pupil_hough_param1"] + self.pupil_hough_param2 = cfg["pupil_hough_param2"] + self.pupil_hough_minimum = cfg["pupil_hough_minimum"] + self.pupil_iris_max_ratio = cfg["pupil_iris_max_ratio"] + self.max_pupil_iris_shift = cfg["max_pupil_iris_shift"] + self.visMinAgreedBits = cfg["vis_min_agreed_bits"] + self.vis_mode = cfg["vis_mode"] + self.NET_INPUT_SIZE = (320,240) + # Loading the CCNet + if not self.use_hough: + if self.mask_model_path and self.circle_model_path: + self.circle_model = models.convnext_tiny() + self.circle_model.avgpool = conv(in_channels=768, out_n=6) + self.circle_model.classifier = fclayer(in_h=7, in_w=10, out_n=6) + try: + self.circle_model.load_state_dict(torch.load(self.circle_model_path, map_location=self.device)) + except AssertionError: + print("assertion error") + self.circle_model.load_state_dict(torch.load(self.circle_model_path, + map_location = lambda storage, loc: storage)) + self.circle_model = self.circle_model.to(self.device) + self.circle_model.eval() + self.mask_model = NestedSharedAtrousResUNet(1, 1, width=64, resolution=(240,320)) + try: + self.mask_model.load_state_dict(torch.load(self.mask_model_path, map_location=self.device)) + except AssertionError: + print("assertion error") + self.mask_model.load_state_dict(torch.load(self.mask_model_path, + map_location = lambda storage, loc: storage)) + self.mask_model = self.mask_model.to(self.device) + self.mask_model.eval() + self.input_transform_mask = Compose([ + ToTensor(), + Normalize(mean=(0.5,), std=(0.5,)) + ]) + self.input_transform_circ = Compose([ + ToTensor(), + Normalize(mean=(0.5,), std=(0.5,)) + ]) + else: + self.CCNET_NUM_CHANNELS = 1 + self.CCNET_NUM_CLASSES = 2 + self.model = UNet(self.CCNET_NUM_CLASSES, self.CCNET_NUM_CHANNELS) + if self.ccnet_model_path: + try: + if self.cuda: + self.model.load_state_dict(torch.load(self.ccnet_model_path, map_location=torch.device('cuda'))) + else: + self.model.load_state_dict(torch.load(self.ccnet_model_path, map_location=torch.device('cpu'))) + print("model state loaded") + except AssertionError: + print("assertion error") + self.model.load_state_dict(torch.load(self.ccnet_model_path, + map_location = lambda storage, loc: storage)) + else: + print("Please provide the CCNet model weight.") + self.model.eval() + + + self.softmax = nn.LogSoftmax(dim=1) + self.input_transform = Compose([ToTensor(),]) + + avg_bits_by_filter_size = {5: 25056, 7: 24463, 9: 23764, 11: 23010, 13: 22225, 15: 21420, 17: 20603, 19: 19777, 21: 18945, 27: 16419, 33: 13864, 39: 11289} + self.avg_num_bits = avg_bits_by_filter_size[self.filter_size] + self.ISO_RES = (640,480) + self.multiproc = cfg["use_multiprocessing_jitter"] + self.num_workers = cfg["num_workers"] + + self.se = np.ones((3, 3), dtype="uint8") + self.sk = np.ones((3, 3), dtype="uint8") + self.clahe = cv2.createCLAHE(clipLimit=5) + + # converts non-ISO images into ISO dimensions + def fix_image(self, image): + w, h = image.size + aspect_ratio = float(w)/float(h) + if aspect_ratio >= 1.333 and aspect_ratio <= 1.334: + result_im = image.resize(self.ISO_RES) + elif aspect_ratio < 1.333: + w_new = h * (4.0/3.0) + w_pad = (w_new - w) / 2 + result_im = Image.new(image.mode, (int(w_new), h), 127) + result_im.paste(image, (int(w_pad), 0)) + result_im = result_im.resize(self.ISO_RES) + else: + h_new = w * (3.0/4.0) + h_pad = (h_new - h) / 2 + result_im = Image.new(image.mode, (w, int(h_new)), 127) + result_im.paste(image, (0, int(h_pad))) + result_im = result_im.resize(self.ISO_RES) + return result_im + + ### Use this function for a faster estimation of mask and circle parameters. When use_hough is False, this function uses both the circle approximation and the mask from MCCNet + def segment_and_circApprox(self, image): + pred = self.segment(image) + pupil_xyr, iris_xyr = self.circApprox(pred, image) + return pred, pupil_xyr, iris_xyr + + def segment(self,image): + if self.use_hough: + w,h = image.size + image = cv2.resize(np.array(image), self.NET_INPUT_SIZE, cv2.INTER_LINEAR) + with torch.no_grad(): + outputs = self.model(Variable(self.input_transform(image).unsqueeze(0).to(self.device))) + logprob = self.softmax(outputs).data.cpu().numpy() + pred = np.argmax(logprob, axis=1)*255 + pred = Image.fromarray(pred[0].astype(np.uint8), 'L') + # Resize the mask to the original image size + pred = cv2.resize(np.uint8(pred), (w,h), cv2.INTER_NEAREST_EXACT) + + # Optional: uncomment the following lines to take only the biggest blob returned by CCNet + ''' + nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(pred, connectivity=4) + if nb_components > 1: + sizes = stats[:, -1] + max_label = 1 + max_size = sizes[1] + for i in range(2, nb_components): + if sizes[i] > max_size: + max_label = i + max_size = sizes[i] + + pred = np.zeros(output.shape) + pred[output == max_label] = 255 + pred = np.asarray(pred, dtype=np.uint8) + ''' + return pred + else: + w,h = image.size + image = cv2.resize(np.array(image), self.NET_INPUT_SIZE, cv2.INTER_LINEAR_EXACT) + with torch.no_grad(): + mask_logit_t = self.mask_model(Variable(self.input_transform_mask(image).unsqueeze(0).to(self.device)))[0] + mask_t = torch.where(torch.sigmoid(mask_logit_t) > 0.5, 255, 0) + mask = mask_t.cpu().numpy()[0] + mask = cv2.resize(np.uint8(mask), (w, h), interpolation=cv2.INTER_NEAREST_EXACT) + + return mask + + + def segmentVis(self,im,mask,pupil_xyr,iris_xyr): + pupil_xyr = np.around(pupil_xyr).astype(np.int32) + iris_xyr = np.around(iris_xyr).astype(np.int32) + imVis = np.stack((np.array(im),)*3, axis=-1) + imVis[:,:,1] = np.clip(imVis[:,:,1] + (96/255)*mask,0,255) + imVis = cv2.circle(imVis, (pupil_xyr[0],pupil_xyr[1]), pupil_xyr[2], (0, 0, 255), 2) + imVis = cv2.circle(imVis, (iris_xyr[0],iris_xyr[1]), iris_xyr[2], (255, 0, 0), 2) + + return imVis + + + def circApprox(self,mask=None,image=None): + if self.use_hough and (mask is None): + print('Please provide mask if you want to use hough transform') + if (not self.use_hough) and (image is None): + print('Please provide image if you want to use the mccnet model') + if self.use_hough and (mask is not None): + # Iris boundary approximation + mask_for_iris = cv2.bitwise_not(mask) + iris_indices = np.nonzero(mask) + if len(iris_indices[0]) == 0: + return None, None + y_span = max(iris_indices[0]) - min(iris_indices[0]) + x_span = max(iris_indices[1]) - min(iris_indices[1]) + + iris_radius_estimate = np.max((x_span,y_span)) // 2 + iris_circle = cv2.HoughCircles(mask_for_iris, cv2.HOUGH_GRADIENT, 1, 50, + param1=self.iris_hough_param1, + param2=self.iris_hough_param2, + minRadius=iris_radius_estimate-self.iris_hough_margin, + maxRadius=iris_radius_estimate+self.iris_hough_margin) + if iris_circle is None: + return None, None + iris_x, iris_y, iris_r = np.round(np.array(iris_circle[0][0])).astype(int) + + + # Pupil boundary approximation + pupil_circle = cv2.HoughCircles(mask_for_iris, cv2.HOUGH_GRADIENT, 1, 50, + param1=self.pupil_hough_param1, + param2=self.pupil_hough_param2, + minRadius=self.pupil_hough_minimum, + maxRadius=np.int32(self.pupil_iris_max_ratio*iris_r)) + if pupil_circle is None: + return None, None + pupil_x, pupil_y, pupil_r = np.round(np.array(pupil_circle[0][0])).astype(int) + + if np.sqrt((pupil_x-iris_x)**2+(pupil_y-iris_y)**2) > self.max_pupil_iris_shift: + pupil_x = iris_x + pupil_y = iris_y + pupil_r = iris_r // 3 + + return np.array([pupil_x,pupil_y,pupil_r]), np.array([iris_x,iris_y,iris_r]) + elif (not self.use_hough) and (image is not None): + w,h = image.size + + image = cv2.resize(np.array(image), self.NET_INPUT_SIZE, cv2.INTER_LINEAR_EXACT) + with torch.no_grad(): + inp_xyr_t = self.circle_model(Variable(self.input_transform_circ(image).unsqueeze(0).repeat(1,3,1,1).to(self.device))) + + #Circle params + diag = math.sqrt(w**2 + h**2) + inp_xyr = inp_xyr_t.tolist()[0] + pupil_x = (inp_xyr[0] * w) + pupil_y = (inp_xyr[1] * h) + pupil_r = (inp_xyr[2] * 0.5 * 0.8 * diag) + iris_x = (inp_xyr[3] * w) + iris_y = (inp_xyr[4] * h) + iris_r = (inp_xyr[5] * 0.5 * diag) + + return np.array([pupil_x,pupil_y,pupil_r]), np.array([iris_x,iris_y,iris_r]) + + # New Rubbersheet model-based Cartesian-to-polar transformation. It has support for bilinear interpolation + def grid_sample(self, input, grid, interp_mode): #helper function for new interpolation + # grid: [-1, 1] + N, C, H, W = input.shape + gridx = grid[:, :, :, 0] + gridy = grid[:, :, :, 1] + gridx = ((gridx + 1) / 2 * W - 0.5) / (W - 1) * 2 - 1 + gridy = ((gridy + 1) / 2 * H - 0.5) / (H - 1) * 2 - 1 + newgrid = torch.stack([gridx, gridy], dim=-1) + return torch.nn.functional.grid_sample(input, newgrid, mode=interp_mode, align_corners=True) + + def Biomech(self, ri0, ri1, numVertices): + + ro = .006 + + vertices = np.linspace((ri1/ro),(ro/ro),numVertices) + h = vertices[2]-vertices[1] + + elements = [] + + boundaries = np.zeros((len(vertices), len(vertices))) + + Er = 4000 + Et = 2970 + nu = 0.49 # The Poisson variable. + + zeta = Et / Er + + eta = Er / (1 - zeta * (nu ** 2)) + + chi = Et / (1 - zeta * (nu ** 2)) + + U = np.zeros((len(vertices),1)) + U_linear = U + + resThresh = 1e-7 + + eta2 = ri1/ro #Normalized final radius. This is different from the eta with the material parameters. + lamda = (ri1-ri0)/ro #This might be need to be modified to incorporate the various changes. This isn't quite fully the equation. + + for q in range(100): + K = np.zeros((len(vertices), len(vertices))) + K[0,0] = 1 + K[len(vertices)-1,len(vertices)-1] = 1 + + f = np.zeros((len(vertices),1)) + f[0] = 1 + f[len(vertices)-1] = 0 + + for i in range(1, len(vertices)-1): + Umid = np.array([(U[i-1]+U[i])/2, (U[i]+U[i+1])/2]) + dU_dr = np.array([(U[i]-U[i-1])/(h), (U[i+1]-U[i])/(h)]) + + for j in range(i-1, i+2): + + start = max(i-1,j-1) + stop = min(i,j) + if start <= stop: + step = 1 + else: + step = -1 + for k in range(start, stop+1, step): + + basis_i = 0.5 + basis_j = 0.5 + + if k == i-1: + dbi_dr = 1/h + else: + dbi_dr = -1/h + + if k == j-1: + dbj_dr = 1/h + else: + dbj_dr = -1/h + + r = vertices[k]+h/2 + + k_local = k - (i-1) + + omega = (nu * eta * zeta - chi) / r + psi = (eta - nu * chi) / r + + alpha = eta / r + + beta = -chi / (r * r) + + Kijk = basis_i * basis_j * (Umid[k_local] * (lamda*(nu+1)*zeta/(2*r**3)) - zeta/r**2) + \ + basis_i * dbj_dr * (dU_dr[k_local]*(-(1-nu*zeta)/(2*r)) - Umid[k_local] * (nu*zeta*lamda/r**2) + 1/r) + \ + dbi_dr * dbj_dr * (-1 + lamda/2*dU_dr[k_local]) + K[i,j] = K[i,j] + h * Kijk + + res = np.linalg.norm(K @ U - f) + if (res < resThresh): + break + + Unew = np.linalg.inv(K) @ f + U = U + (Unew - U) + + if np.linalg.norm(U_linear) == 0: + U_linear = U + UR = U * (ri1 - ri0) + R = np.linspace(ri0, ro, numVertices) + UR.reshape(1,numVertices) + normR = R - R.min() + normR = normR / normR.max() + + return normR + + def bioMechCartToPol_torch(self, image, mask, pupil_xyr, iris_xyr, radiusBiomech, interpolation='bilinear'): + with torch.no_grad(): + if pupil_xyr is None or iris_xyr is None: + return None, None + + image = torch.tensor(np.array(image)).float().unsqueeze(0).unsqueeze(0).to(self.device) + mask = torch.tensor(np.array(mask)).float().unsqueeze(0).unsqueeze(0).to(self.device) + width = image.shape[3] + height = image.shape[2] + + polar_height = self.polar_height + polar_width = self.polar_width + pupil_xyr = torch.tensor(pupil_xyr).unsqueeze(0).float().to(self.device) + iris_xyr = torch.tensor(iris_xyr).unsqueeze(0).float().to(self.device) + + theta = (2*pi*torch.linspace(1,polar_width,polar_width)/polar_width).to(self.device) + pxCirclePoints = pupil_xyr[:, 0].reshape(-1, 1) + pupil_xyr[:, 2].reshape(-1, 1) @ torch.cos(theta).reshape(1, polar_width) #b x 512 + pyCirclePoints = pupil_xyr[:, 1].reshape(-1, 1) + pupil_xyr[:, 2].reshape(-1, 1) @ torch.sin(theta).reshape(1, polar_width) #b x 512 + + ixCirclePoints = iris_xyr[:, 0].reshape(-1, 1) + iris_xyr[:, 2].reshape(-1, 1) @ torch.cos(theta).reshape(1, polar_width) #b x 512 + iyCirclePoints = iris_xyr[:, 1].reshape(-1, 1) + iris_xyr[:, 2].reshape(-1, 1) @ torch.sin(theta).reshape(1, polar_width) #b x 512 + + radius = torch.tensor(radiusBiomech).float().reshape(-1, 1).to(self.device) #64 x 1 + + pxCoords = torch.matmul((1-radius), pxCirclePoints.reshape(-1, 1, polar_width)).to(self.device) # b x 64 x 512 + pyCoords = torch.matmul((1-radius), pyCirclePoints.reshape(-1, 1, polar_width)).to(self.device) # b x 64 x 512 + + ixCoords = torch.matmul(radius, ixCirclePoints.reshape(-1, 1, polar_width)).to(self.device) # b x 64 x 512 + iyCoords = torch.matmul(radius, iyCirclePoints.reshape(-1, 1, polar_width)).to(self.device) # b x 64 x 512 + + x = (pxCoords + ixCoords).float() + x_norm = ((x-1)/(width-1))*2 - 1 #b x 64 x 512 + + y = (pyCoords + iyCoords).float() + y_norm = ((y-1)/(height-1))*2 - 1 #b x 64 x 512 + + grid_sample_mat = torch.cat([x_norm.unsqueeze(-1), y_norm.unsqueeze(-1)], dim=-1).to(self.device) + + image_polar = self.grid_sample(image, grid_sample_mat, interp_mode=interpolation).to(self.device) + image_polar = torch.clamp(torch.round(image_polar), min=0, max=255) + mask_polar = self.grid_sample(mask, grid_sample_mat, interp_mode='nearest') + mask_polar = (mask_polar>0.5).long() * 255 + + return (image_polar[0][0].cpu().numpy()).astype(np.uint8), mask_polar[0][0].cpu().numpy().astype(np.uint8) + + + def cartToPol_torch(self, image, mask, pupil_xyr, iris_xyr, interpolation='bilinear'): # + with torch.no_grad(): + if pupil_xyr is None or iris_xyr is None: + return None, None + + image = torch.tensor(np.array(image)).float().unsqueeze(0).unsqueeze(0).to(self.device) + mask = torch.tensor(np.array(mask)).float().unsqueeze(0).unsqueeze(0).to(self.device) + width = image.shape[3] + height = image.shape[2] + + polar_height = self.polar_height + polar_width = self.polar_width + pupil_xyr = torch.tensor(pupil_xyr).unsqueeze(0).float().to(self.device) + iris_xyr = torch.tensor(iris_xyr).unsqueeze(0).float().to(self.device) + + theta = (2*pi*torch.linspace(0,polar_width-1,polar_width)/polar_width).to(self.device) + pxCirclePoints = (pupil_xyr[:, 0].reshape(-1, 1) + pupil_xyr[:, 2].reshape(-1, 1) @ torch.cos(theta).reshape(1, polar_width)).to(self.device) #b x 512 + pyCirclePoints = (pupil_xyr[:, 1].reshape(-1, 1) + pupil_xyr[:, 2].reshape(-1, 1) @ torch.sin(theta).reshape(1, polar_width)).to(self.device) #b x 512 + + ixCirclePoints = (iris_xyr[:, 0].reshape(-1, 1) + iris_xyr[:, 2].reshape(-1, 1) @ torch.cos(theta).reshape(1, polar_width)).to(self.device) #b x 512 + iyCirclePoints = (iris_xyr[:, 1].reshape(-1, 1) + iris_xyr[:, 2].reshape(-1, 1) @ torch.sin(theta).reshape(1, polar_width)).to(self.device) #b x 512 + + radius = (torch.linspace(1,polar_height,polar_height)/polar_height).reshape(-1, 1).to(self.device) #64 x 1 + + pxCoords = torch.matmul((1-radius), pxCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + pyCoords = torch.matmul((1-radius), pyCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + + ixCoords = torch.matmul(radius, ixCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + iyCoords = torch.matmul(radius, iyCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + + x = (pxCoords + ixCoords).float() + x_norm = ((x-1)/(width-1))*2 - 1 #b x 64 x 512 + + y = (pyCoords + iyCoords).float() + y_norm = ((y-1)/(height-1))*2 - 1 #b x 64 x 512 + + grid_sample_mat = torch.cat([x_norm.unsqueeze(-1), y_norm.unsqueeze(-1)], dim=-1).to(self.device) + + image_polar = self.grid_sample(image, grid_sample_mat, interp_mode=interpolation) + image_polar = torch.clamp(torch.round(image_polar), min=0, max=255) + mask_polar = self.grid_sample(mask, grid_sample_mat, interp_mode='nearest') # always use nearest neighbor interpolation for mask + mask_polar = (mask_polar>0.5).long() * 255 + + return (image_polar[0][0].cpu().numpy()).astype(np.uint8), mask_polar[0][0].cpu().numpy().astype(np.uint8) + + def cartToPol_torch_jitter(self, image, mask, pupil_xyr, iris_xyr, interpolation='bilinear'): # + with torch.no_grad(): + if pupil_xyr is None or iris_xyr is None: + return None, None + + image = torch.tensor(np.array(image)).float().unsqueeze(0).unsqueeze(0).to(self.device) + mask = torch.tensor(np.array(mask)).float().unsqueeze(0).unsqueeze(0).to(self.device) + width = image.shape[3] + height = image.shape[2] + + polar_height = self.polar_height + polar_width = self.polar_width + pupil_xyr = torch.tensor(pupil_xyr).unsqueeze(0).float().to(self.device) + iris_xyr = torch.tensor(iris_xyr).unsqueeze(0).float().to(self.device) + + theta = (2*pi*torch.linspace(0,polar_width-1,polar_width)/polar_width).to(self.device) + radius = (torch.linspace(1,polar_height,polar_height)/polar_height).reshape(-1, 1).to(self.device) #64 x 1 + + images_polar = [] + masks_polar = [] + + for pxj in range(-self.jitter, self.jitter+1): + for pyj in range(-self.jitter, self.jitter+1): + for ixj in range(-self.jitter, self.jitter+1): + for iyj in range(-self.jitter, self.jitter+1): + for mxj in range(-self.jitter, self.jitter+1): + for myj in range(-self.jitter, self.jitter+1): + + mask_jitter = mask.clone().detach() + + mask_jitter = torch.roll(mask_jitter, mxj, dims=3) + + if mxj > 0: + mask_jitter[:,:,:,:mxj] = 0.0 + elif mxj < 0: + mask_jitter[:,:,:,mxj:] = 0.0 + + mask_jitter = torch.roll(mask_jitter, myj, dims=2) + + if myj > 0: + mask_jitter[:,:,:myj,:] = 0.0 + elif myj < 0: + mask_jitter[:,:,myj:,:] = 0.0 + + pxyr_jitter = pupil_xyr.clone().detach() + ixyr_jitter = iris_xyr.clone().detach() + + pxyr_jitter[:, 0] += pxj + pxyr_jitter[:, 1] += pyj + + ixyr_jitter[:, 0] += ixj + ixyr_jitter[:, 1] += iyj + + pxCirclePoints = (pxyr_jitter[:, 0].reshape(-1, 1) + pxyr_jitter[:, 2].reshape(-1, 1) @ torch.cos(theta).reshape(1, polar_width)).to(self.device) #b x 512 + pyCirclePoints = (pxyr_jitter[:, 1].reshape(-1, 1) + pxyr_jitter[:, 2].reshape(-1, 1) @ torch.sin(theta).reshape(1, polar_width)).to(self.device) #b x 512 + + ixCirclePoints = (ixyr_jitter[:, 0].reshape(-1, 1) + ixyr_jitter[:, 2].reshape(-1, 1) @ torch.cos(theta).reshape(1, polar_width)).to(self.device) #b x 512 + iyCirclePoints = (ixyr_jitter[:, 1].reshape(-1, 1) + ixyr_jitter[:, 2].reshape(-1, 1) @ torch.sin(theta).reshape(1, polar_width)).to(self.device) #b x 512 + + pxCoords = torch.matmul((1-radius), pxCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + pyCoords = torch.matmul((1-radius), pyCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + + ixCoords = torch.matmul(radius, ixCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + iyCoords = torch.matmul(radius, iyCirclePoints.reshape(-1, 1, polar_width)) # b x 64 x 512 + + x = (pxCoords + ixCoords).float() + x_norm = ((x-1)/(width-1))*2 - 1 #b x 64 x 512 + + y = (pyCoords + iyCoords).float() + y_norm = ((y-1)/(height-1))*2 - 1 #b x 64 x 512 + + grid_sample_mat = torch.cat([x_norm.unsqueeze(-1), y_norm.unsqueeze(-1)], dim=-1).to(self.device) + + image_polar = self.grid_sample(image, grid_sample_mat, interp_mode=interpolation) + image_polar = torch.clamp(torch.round(image_polar), min=0, max=255) + mask_polar = self.grid_sample(mask_jitter, grid_sample_mat, interp_mode='nearest') # always use nearest neighbor interpolation for mask + mask_polar = (mask_polar>0.5).long() * 255 + + images_polar.append(image_polar[0][0].cpu().numpy().astype(np.uint8)) + masks_polar.append(mask_polar[0][0].cpu().numpy().astype(np.uint8)) + + return images_polar, masks_polar + + # (Fixed) Old implementation of Rubbersheet model-based Cartesian-to-polar transformation that uses nearest neighbor interpolation + def cartToPol(self, image, mask, pupil_xyr, iris_xyr): + + if pupil_xyr is None: + return None, None + + image = np.array(image) + height, width = image.shape + mask = np.array(mask) + + image_polar = np.zeros((self.polar_height, self.polar_width), np.uint8) + mask_polar = np.zeros((self.polar_height, self.polar_width), np.uint8) + + theta = 2*pi*np.linspace(0,self.polar_width-1,self.polar_width)/self.polar_width + + pxCirclePoints = np.around(pupil_xyr[0] + pupil_xyr[2]*np.cos(theta)) + ixCirclePoints = np.around(iris_xyr[0] + iris_xyr[2]*np.cos(theta)) + pyCirclePoints = np.around(pupil_xyr[1] + pupil_xyr[2]*np.sin(theta)) + iyCirclePoints = np.around(iris_xyr[1] + iris_xyr[2]*np.sin(theta)) + + for j in range(1, self.polar_width+1): + for i in range(1, self.polar_height+1): + + radius = i/self.polar_height + x = int(np.around((1-radius) * pxCirclePoints[j-1] + radius * ixCirclePoints[j-1])) + y = int(np.around((1-radius) * pyCirclePoints[j-1] + radius * iyCirclePoints[j-1])) + if (x > 0 and x <= width and y > 0 and y <= height): + image_polar[i-1][j-1] = image[y-1][x-1] + mask_polar[i-1][j-1] = mask[y-1][x-1] + + return image_polar, mask_polar + + def extractCode(self, polar): + with torch.no_grad(): + if polar is None: + return None + r = int(np.floor(self.filter_size / 2)) + polar_t = torch.tensor(polar).float().unsqueeze(0).unsqueeze(0).to(self.device) + padded_polar = nn.functional.pad(polar_t, (r, r, 0, 0), mode='circular') + codeContinuous = nn.functional.conv2d(padded_polar, self.torch_filter) + codeBinary = torch.where(codeContinuous.squeeze(0) > 0, True, False).cpu().numpy() + return codeBinary # The size of the code should be: 7 x (64 - filter_size) x 512 + + def extractMultipleCodes(self, polars): + with torch.no_grad(): + polars_t = [] + for polar in polars: + if polar is None: + print("One of the polar images is None. Unable to continue...") + return None + r = int(np.floor(self.filter_size / 2)) + polar_t = torch.tensor(polar).float().unsqueeze(0).unsqueeze(0).to(self.device) + polars_t.append(polar_t) + polars_t = torch.cat(polars_t, 0) + padded_polar = nn.functional.pad(polars_t, (r, r, 0, 0), mode='circular') + codesContinuous = nn.functional.conv2d(padded_polar, self.torch_filter) + codesBinary = torch.where(codesContinuous > 0, True, False).cpu().numpy() + codes = [] + for i in range(codesBinary.shape[0]): + codes.append(codesBinary[i]) # The size of the code should be: 7 x (64 - filter_size) x 512 + return codes + + def findMajorityVoteCode(self, codes, masks_polar): + masked_codes = [] + masks_polar_binary = [] + r = int(np.floor(self.filter_size / 2)) + for code, mask_polar in zip(codes, masks_polar): + mask_polar_binary = np.where(mask_polar > 127, True, False) + masks_polar_binary.append(mask_polar_binary.astype(int)) + masked_codes.append(np.logical_and(code, mask_polar_binary[r:-r, :]).astype(int)) + masked_codes_sum = sum(masked_codes) + masks_polar_sum = sum(masks_polar_binary) + majorityVoteCode = np.where(masked_codes_sum > (masks_polar_sum[r:-r, :] * 0.5), True, False) + mask_polar = np.where(masks_polar_sum > (len(masks_polar_binary)/2.0), 255, 0) + return majorityVoteCode, mask_polar.astype(np.uint8) + + def matchCodes(self, code1, code2, mask1, mask2): + r = int(np.floor(self.filter_size / 2)) + # Cutting off mask to (64-filter_size+1) x 512 and binarizing it. + mask1_binary = np.where(mask1[r:-r, :] > 127, True, False) + mask2_binary = np.where(mask2[r:-r, :] > 127, True, False) + if (np.sum(mask1_binary) <= self.threshold_frac_avg_bits * self.avg_num_bits) or (np.sum(mask2_binary) <= self.threshold_frac_avg_bits * self.avg_num_bits): + print("Too small masks") + return -1.0, -1.0 + scoreC = [] + for xshift in range(-self.max_shift, self.max_shift+1): + andMasks = np.logical_and(mask1_binary, np.roll(mask2_binary, xshift, axis=1)) + if np.sum(andMasks) == 0: + scoreC.append(float('inf')) + else: + xorCodes = np.logical_xor(code1, np.roll(code2, xshift, axis=2)) + xorCodesMasked = np.logical_and(xorCodes, np.tile(np.expand_dims(andMasks,axis=0), (self.num_filters, 1, 1))) + scoreC.append(np.sum(xorCodesMasked) / (np.sum(andMasks) * self.num_filters)) + if self.score_norm: + scoreC[-1] = 0.5 - (0.5 - scoreC[-1]) * math.sqrt( np.sum(andMasks) / self.avg_num_bits ) + scoreC_index = np.argmin(np.array(scoreC)) + scoreC_best = scoreC[scoreC_index] + if scoreC_best == float('inf'): + print("Too small overlap between masks") + return -1.0, -1.0 + scoreC_shift = scoreC_index - self.max_shift + + return scoreC_best, scoreC_shift + + def matchCodesJitter(self, codes1, code2, masks1, mask2): + if self.multiproc: + inputs = [] + for code1, mask1 in zip(codes1, masks1): + inputs.append((self, code1, code2, mask1, mask2)) + with multiprocessing.Pool(self.num_workers) as p: + scores = p.starmap(matchCodesGlobal, inputs) + return np.min(np.array(scores)) + else: + scores = [] + for code1, mask1 in zip(codes1, masks1): + scores.append(self.matchCodes(code1, code2, mask1, mask2)) + return np.min(np.array(scores)) + + def matchCodesShiftXY(self, code1, code2, mask1, mask2): + r = int(np.floor(self.filter_size / 2)) + # Cutting off mask to (64-filter_size+1) x 512 and binarizing it. + mask1_binary = np.where(mask1[r:-r, :] > 127, True, False) + mask2_binary = np.where(mask2[r:-r, :] > 127, True, False) + if (np.sum(mask1_binary) <= self.threshold_frac_avg_bits * self.avg_num_bits) or (np.sum(mask2_binary) <= self.threshold_frac_avg_bits * self.avg_num_bits): + return -1.0, -1.0 + scoreC = [] + for xshift in range(-self.max_shift, self.max_shift+1): + for ytrans in range(-int(round(self.max_shift/8)), int(round(self.max_shift/8))+1): + if xshift != 0: + mask2_binary_shifted = np.roll(mask2_binary, xshift, axis=1) + else: + mask2_binary_shifted = mask2_binary + if ytrans != 0: + mask2_binary_shifted = np.roll(mask2_binary_shifted, ytrans, axis=0) + if ytrans > 0: + mask2_binary_shifted[:ytrans, :] = 0 + elif ytrans < 0: + mask2_binary_shifted[ytrans:, :] = 0 + andMasks = np.logical_and(mask1_binary, mask2_binary_shifted) + if np.sum(andMasks) == 0: + scoreC.append(float('inf')) + else: + xorCodes = np.logical_xor(code1, np.roll(np.roll(code2, xshift, axis=2), ytrans, axis=1)) + xorCodesMasked = np.logical_and(xorCodes, np.tile(np.expand_dims(andMasks,axis=0), (self.num_filters, 1, 1))) + scoreC.append(np.sum(xorCodesMasked) / (np.sum(andMasks) * self.num_filters)) + if self.score_norm: + scoreC[-1] = 0.5 - (0.5 - scoreC[-1]) * math.sqrt( np.sum(andMasks) / self.avg_num_bits ) + scoreC_index = np.argmin(np.array(scoreC)) + scoreC_best = scoreC[scoreC_index] + if scoreC_best == float('inf'): + return -1.0, -1.0 + + shift_divide = int(round(self.max_shift/8)) * 2 + 1 + scoreC_shift = int(scoreC_index / shift_divide) - self.max_shift + + return scoreC_best, scoreC_shift + + def polar(self,x,y): + return math.hypot(x,y),math.degrees(math.atan2(y,x)) + + def visualizeMatchingResult(self, code1, code2, mask1, mask2, shift, im, pupil_xyr, iris_xyr): + + resMask = np.zeros((self.ISO_RES[1],self.ISO_RES[0])) + + r = int(np.floor(self.filter_size / 2)) + # calculate heat map + xorCodes = np.logical_xor(code1, np.roll(code2, self.max_shift-shift, axis=1)) + mask1_binary = np.where(mask1[r:-r, :] > 127, True, False) + mask2_binary = np.where(mask2[r:-r, :] > 127, True, False) + andMasks = np.logical_and(mask1_binary, np.roll(mask2_binary, shift, axis=1)) + + heatMap = 1-xorCodes.astype(int) + heatMap = np.pad(np.mean(heatMap,axis=0), pad_width=((8,8),(0,0)), mode='constant', constant_values=0) + andMasks = np.pad(andMasks, pad_width=((8,8),(0,0)), mode='constant', constant_values=0) + + heatMap = heatMap * andMasks + + if 'single' in self.vis_mode: + heatMap = (heatMap >= self.visMinAgreedBits / 100).astype(np.uint8) + + heatMap = np.roll(heatMap,int(self.polar_width/2),axis=1) + + for j in range(self.ISO_RES[0]): + for i in range(self.ISO_RES[1]): + xi = j-iris_xyr[0] + yi = i-iris_xyr[1] + ri = iris_xyr[2] + xp = j-pupil_xyr[0] + yp = i-pupil_xyr[1] + rp = pupil_xyr[2] + + if xi**2 + yi**2 < ri**2 and xp**2 + yp**2 > rp**2: + rr,tt = self.polar(xi,yi) + tt = np.clip(np.round(self.polar_width*((180+tt)/360)).astype(int),0,self.polar_width-1) + rr = np.clip(np.round(self.polar_height * (rr - rp) / (ri - rp)).astype(int),0,self.polar_height-1) + resMask[i,j] = heatMap[rr,tt] # *** TODO correct mapping for shifted p/i centers + + heatMap = 255*cv2.morphologyEx(resMask, cv2.MORPH_OPEN, kernel=self.se) + mask_blur = cv2.filter2D(heatMap,-1,self.sk) + + if 'single' in self.vis_mode: + mask_blur = (48 * mask_blur / np.max(mask_blur)).astype(int) + imVis = np.stack((np.array(im),)*3, axis=-1) + imVis[:,:,1] = np.clip(imVis[:,:,1] + mask_blur,0,255) + elif 'heat_map' in self.vis_mode: + mask_blur = (255 * mask_blur / np.max(mask_blur)).astype(int) + heatMap = np.uint8(np.stack((np.array(mask_blur),)*3, axis=-1)) + heatMap = cv2.applyColorMap(heatMap, cv2.COLORMAP_JET) + cl_im = self.clahe.apply(np.array(im)) + imVis = np.stack((cl_im,)*3, axis=-1) + imVis = cv2.addWeighted(heatMap, 0.1, np.array(imVis), 0.9, 32) + else: + raise Exception("Unknown visualization mode") + + return imVis diff --git a/pdm/modules/network.py b/pdm/modules/network.py new file mode 100644 index 0000000..38f1484 --- /dev/null +++ b/pdm/modules/network.py @@ -0,0 +1,270 @@ +import torch +import torch.nn as nn +import torch.nn.init as init + +import math +import numpy as np + +class UNetUp(nn.Module): + + def __init__(self, in_channels, features, out_channels, is_bn = True): + super().__init__() + if is_bn: + self.up = nn.Sequential( + nn.Conv2d(in_channels, features, 3, padding = 1), + nn.BatchNorm2d(features), + nn.ReLU(inplace=True), + nn.Conv2d(features, features, 3, padding = 1), + nn.BatchNorm2d(features), + nn.ReLU(inplace=True), + nn.ConvTranspose2d(features, out_channels, 2, stride=2), + nn.BatchNorm2d(out_channels), + nn.ReLU(inplace=True) + ) + else: + self.up = nn.Sequential( + nn.Conv2d(in_channels, features, 3, padding = 1), + nn.ReLU(inplace=True), + nn.Conv2d(features, features, 3, padding = 1), + nn.ReLU(inplace=True), + nn.ConvTranspose2d(features, out_channels, 2, stride=2), + nn.ReLU(inplace=True) + ) + + def forward(self, x): + return self.up(x) + + +class UNetDown(nn.Module): + + def __init__(self, in_channels, out_channels, is_bn = True): + super().__init__() + + if is_bn: + self.down = nn.Sequential( + nn.MaxPool2d(2, stride=2), + nn.Conv2d(in_channels, out_channels, 3, padding = 1), + nn.BatchNorm2d(out_channels), + nn.ReLU(inplace=True), + nn.Conv2d(out_channels, out_channels, 3, padding = 1), + nn.BatchNorm2d(out_channels), + nn.ReLU(inplace=True) + ) + else: + self.down = nn.Sequential( + nn.MaxPool2d(2, stride=2), + nn.Conv2d(in_channels, out_channels, 3, padding = 1), + nn.ReLU(inplace=True), + nn.Conv2d(out_channels, out_channels, 3, padding = 1), + nn.ReLU(inplace=True) + ) + + def forward(self, x): + return self.down(x) + +class UNet(nn.Module): + + def __init__(self, num_classes, num_channels): + super().__init__() + + self.first = nn.Sequential( + nn.Conv2d(num_channels, 4, 3, padding = 1), + nn.BatchNorm2d(4), + nn.ReLU(inplace=True), + nn.Conv2d(4, 4, 3, padding = 1), + nn.BatchNorm2d(4), + nn.ReLU(inplace=True) + ) + self.dec2 = UNetDown(4, 8) + self.dec3 = UNetDown(8, 16) + self.dec4 = UNetDown(16, 32) + + self.center = nn.MaxPool2d(2, stride=2) + self.center1 = nn.Conv2d(32, 64, 3, padding = 1) + self.center_bn = nn.BatchNorm2d(64) + self.center_relu = nn.ReLU(inplace=True) + self.center2 = nn.Conv2d(64, 64, 3, padding = 1) + self.center2_bn = nn.BatchNorm2d(64) + self.center2relu = nn.ReLU(inplace=True) + self.center3 = nn.ConvTranspose2d(64, 32, 2, stride=2) + self.center3_bn = nn.BatchNorm2d(32) + self.center3_relu = nn.ReLU(inplace=True) + + self.enc4 = UNetUp(64, 32, 16) + self.enc3 = UNetUp(32, 16, 8) + self.enc2 = UNetUp(16, 8, 4) + self.enc1 = nn.Sequential( + nn.Conv2d(8, 4, 3, padding = 1), + nn.BatchNorm2d(4), + nn.ReLU(inplace=True), + nn.Conv2d(4, 4, 3, padding = 1), + nn.BatchNorm2d(4), + nn.ReLU(inplace=True), + ) + self.final = nn.Conv2d(4, num_classes, 1) + self._initialize_weights() + + def _initialize_weights(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, np.sqrt(2. / n)) + init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + + def forward(self, x): + dec1 = self.first(x) + dec2 = self.dec2(dec1) + dec3 = self.dec3(dec2) + dec4 = self.dec4(dec3) + + center1 = self.center(dec4) + center2 = self.center1(center1) + center3 = self.center_bn(center2) + center4 = self.center_relu(center3) + center5 = self.center2(center4) + center6 = self.center2_bn(center5) + center7 = self.center2relu(center6) + center8 = self.center3(center7) + center9 = self.center3_bn(center8) + center10 = self.center3_relu(center9) + + enc4 = self.enc4(torch.cat([center10, dec4], 1)) + enc3 = self.enc3(torch.cat([enc4, dec3], 1)) + enc2 = self.enc2(torch.cat([enc3, dec2], 1)) + enc1 = self.enc1(torch.cat([enc2, dec1], 1)) + + return self.final(enc1) + +class SharedAtrousConv2d(nn.Module): + def __init__(self, in_channels, out_channels, bias=True): + super().__init__() + self.weights = nn.Parameter(torch.rand(int(out_channels/2), in_channels, 3, 3)) + nn.init.kaiming_uniform_(self.weights, mode='fan_out', nonlinearity='relu') + if bias: + self.bias1 = nn.Parameter(torch.zeros(int(out_channels/2))) + self.bias2 = nn.Parameter(torch.zeros(int(out_channels/2))) + else: + self.bias1 = None + self.bias2 = None + def forward(self, x): + x1 = nn.functional.conv2d(x, self.weights, stride=1, padding='same', bias=self.bias1) + x2 = nn.functional.conv2d(x, self.weights, stride=1, padding='same', dilation=2, bias=self.bias2) + x3 = torch.cat([x1, x2], 1) + return x3 + +class SharedAtrousResBlock(nn.Module): + def __init__(self, in_channels, middle_channels, out_channels): + super().__init__() + self.conv_res = nn.Sequential( + nn.Conv2d(in_channels, out_channels, 1, stride=1, bias=False), + nn.BatchNorm2d(out_channels) + ) + self.net = nn.Sequential( + SharedAtrousConv2d(in_channels, middle_channels, bias=False), + nn.BatchNorm2d(middle_channels), + nn.ReLU(inplace=True), + SharedAtrousConv2d(middle_channels, out_channels, bias=False), + nn.BatchNorm2d(out_channels) + ) + self.relu = nn.ReLU(inplace=True) + def forward(self, x): + res = self.conv_res(x) + x = self.net(x) + x = (x + res) * (1 / math.sqrt(2)) + x = self.relu(x) + return x + +class Resize(nn.Module): + def __init__(self, size=None, scale_factor=None, mode='nearest', align_corners=None, recompute_scale_factor=None, antialias=False): + super().__init__() + self.size = size + self.scale_factor = scale_factor + self.mode = mode + self.align_corners = align_corners + self.recompute_scale_factor = recompute_scale_factor + self.antialias = antialias + def forward(self, x): + return torch.nn.functional.interpolate(x, size=self.size, scale_factor=self.scale_factor, mode=self.mode, align_corners=self.align_corners, recompute_scale_factor=self.recompute_scale_factor, antialias=self.antialias) + +class NestedSharedAtrousResUNet(nn.Module): + def __init__(self, num_classes, num_channels, width=32, resolution=(240, 320)): + super().__init__() + self.resolution = resolution + nb_filter = [width, width*2, width*4, width*8, width*16] + + self.pool = Resize(scale_factor=0.5, mode='bilinear') + self.up = Resize(scale_factor=2, mode='bilinear') + + self.conv0_0 = SharedAtrousResBlock(num_channels, nb_filter[0], nb_filter[0]) + self.conv1_0 = SharedAtrousResBlock(nb_filter[0], nb_filter[1], nb_filter[1]) + self.conv2_0 = SharedAtrousResBlock(nb_filter[1], nb_filter[2], nb_filter[2]) + self.conv3_0 = SharedAtrousResBlock(nb_filter[2], nb_filter[3], nb_filter[3]) + self.conv4_0 = SharedAtrousResBlock(nb_filter[3], nb_filter[4], nb_filter[4]) + + self.conv0_1 = SharedAtrousResBlock(nb_filter[0]+nb_filter[1], nb_filter[0], nb_filter[0]) + self.conv1_1 = SharedAtrousResBlock(nb_filter[1]+nb_filter[2], nb_filter[1], nb_filter[1]) + self.conv2_1 = SharedAtrousResBlock(nb_filter[2]+nb_filter[3], nb_filter[2], nb_filter[2]) + self.conv3_1 = SharedAtrousResBlock(nb_filter[3]+nb_filter[4], nb_filter[3], nb_filter[3]) + + self.conv0_2 = SharedAtrousResBlock(nb_filter[0]*2+nb_filter[1], nb_filter[0], nb_filter[0]) + self.conv1_2 = SharedAtrousResBlock(nb_filter[1]*2+nb_filter[2], nb_filter[1], nb_filter[1]) + self.conv2_2 = SharedAtrousResBlock(nb_filter[2]*2+nb_filter[3], nb_filter[2], nb_filter[2]) + + self.conv0_3 = SharedAtrousResBlock(nb_filter[0]*3+nb_filter[1], nb_filter[0], nb_filter[0]) + self.conv1_3 = SharedAtrousResBlock(nb_filter[1]*3+nb_filter[2], nb_filter[1], nb_filter[1]) + + self.conv0_4 = SharedAtrousResBlock(nb_filter[0]*4+nb_filter[1], nb_filter[0], nb_filter[0]) + + self.final = nn.Conv2d(nb_filter[0]*4, num_classes, kernel_size=1) + + def forward(self, input): + + x0_0 = self.conv0_0(input) #320x240 + x1_0 = self.conv1_0(self.pool(x0_0)) #160x120 + x0_1 = self.conv0_1(torch.cat([x0_0, self.up(x1_0)], 1)) + + x2_0 = self.conv2_0(self.pool(x1_0)) #80x60 + x1_1 = self.conv1_1(torch.cat([x1_0, self.up(x2_0)], 1)) + x0_2 = self.conv0_2(torch.cat([x0_0, x0_1, self.up(x1_1)], 1)) + + x3_0 = self.conv3_0(self.pool(x2_0)) #40x30 + x2_1 = self.conv2_1(torch.cat([x2_0, self.up(x3_0)], 1)) + x1_2 = self.conv1_2(torch.cat([x1_0, x1_1, self.up(x2_1)], 1)) + x0_3 = self.conv0_3(torch.cat([x0_0, x0_1, x0_2, self.up(x1_2)], 1)) + + x4_0 = self.conv4_0(self.pool(x3_0)) #20x15 + x3_1 = self.conv3_1(torch.cat([x3_0, self.up(x4_0)], 1)) + x2_2 = self.conv2_2(torch.cat([x2_0, x2_1, self.up(x3_1)], 1)) + x1_3 = self.conv1_3(torch.cat([x1_0, x1_1, x1_2, self.up(x2_2)], 1)) + x0_4 = self.conv0_4(torch.cat([x0_0, x0_1, x0_2, x0_3, self.up(x1_3)], 1)) + + output = self.final(torch.cat([x0_1, x0_2, x0_3, x0_4], 1)) + + return output + +class fclayer(nn.Module): + def __init__(self, in_h = 8, in_w = 10, out_n = 6): + super().__init__() + self.in_h = in_h + self.in_w = in_w + self.out_n = out_n + self.fc_list = [] + self.fc_list = nn.ModuleList(nn.Linear(in_h*in_w, 1) for i in range(out_n)) + def forward(self, x): + x = x.reshape(-1, self.out_n, self.in_h, self.in_w) + outs = [] + for i, fc_layer in enumerate(self.fc_list): + outs.append(fc_layer(x[:, i, :, :].reshape(-1, self.in_h*self.in_w))) + out = torch.cat(outs, 1) + return out + +class conv(nn.Module): + def __init__(self, in_channels=512, out_n = 6): + super().__init__() + self.conv = nn.Conv2d(in_channels, out_n, kernel_size=1, stride=1, padding='same') + def forward(self, x): + x = self.conv(x) + return x \ No newline at end of file diff --git a/pdm/modules/transform.py b/pdm/modules/transform.py new file mode 100644 index 0000000..9b1a8d1 --- /dev/null +++ b/pdm/modules/transform.py @@ -0,0 +1,33 @@ +import numpy as np +import torch + +def colormap(n): + cmap=np.zeros([n, 3]).astype(np.uint8) + + for i in np.arange(n): + r, g, b = np.zeros(3) + + for j in np.arange(8): + r = r + (1<<(7-j))*((i&(1<<(3*j))) >> (3*j)) + g = g + (1<<(7-j))*((i&(1<<(3*j+1))) >> (3*j+1)) + b = b + (1<<(7-j))*((i&(1<<(3*j+2))) >> (3*j+2)) + + cmap[i,:] = np.array([r, g, b]) + + return cmap + +class Relabel: + + def __init__(self, olabel, nlabel): + self.olabel = olabel + self.nlabel = nlabel + + def __call__(self, tensor): + tensor[tensor == self.olabel] = self.nlabel + return tensor + + +class ToLabel: + + def __call__(self, image): + return torch.from_numpy(image).unsqueeze(0) diff --git a/pdm/modules/utils.py b/pdm/modules/utils.py new file mode 100644 index 0000000..fcb9a43 --- /dev/null +++ b/pdm/modules/utils.py @@ -0,0 +1,27 @@ +import yaml + +def get_cfg(cfg_path): + cfg = yaml.safe_load(open(cfg_path, 'r')) + return cfg + +def load_CCNet(): + + # CCNet model loading (iris segmentation) + model = UNet(NUM_CLASSES, NUM_CHANNELS) + if args.cuda: + torch.cuda.set_device(args.gpu) + model = model.cuda() + if args.state: + try: + if args.cuda: + model.load_state_dict(torch.load(args.state)) + else: + model.load_state_dict(torch.load(args.state, map_location=torch.device('cpu'))) + except AssertionError: + print("assertion error") + model.load_state_dict(torch.load(args.state, + map_location = lambda storage, loc: storage)) + model.eval() + softmax = nn.LogSoftmax(dim=1) + + return model, softmax \ No newline at end of file diff --git a/pdm/network.py b/pdm/network.py new file mode 100644 index 0000000..ea229df --- /dev/null +++ b/pdm/network.py @@ -0,0 +1,477 @@ +import torch +from torch import nn +import math + +class ResBlockIN(nn.Module): + def __init__(self, in_channels, middle_channels, out_channels, downsample=False): + super(ResBlockIN,self).__init__() + self.conv_res = nn.Sequential( + nn.Conv2d(in_channels, out_channels, 1, stride=1, padding='same', bias=False), + nn.InstanceNorm2d(out_channels) + ) + self.downsample = downsample + if downsample: + self.net_downsample = nn.Sequential( + nn.Conv2d(in_channels, middle_channels, 4, stride=2, padding=1, bias=False), + nn.InstanceNorm2d(middle_channels), + nn.GELU(), + nn.Conv2d(middle_channels, out_channels, 3, stride=1, padding='same', bias=False), + nn.InstanceNorm2d(out_channels) + ) + else: + self.net = nn.Sequential( + nn.Conv2d(in_channels, middle_channels, 3, stride=1, padding='same', bias=False), + nn.InstanceNorm2d(middle_channels), + nn.GELU(), + nn.Conv2d(middle_channels, out_channels, 3, stride=1, padding='same', bias=False), + nn.InstanceNorm2d(out_channels) + ) + self.act = nn.GELU() + def forward(self, x): + res = self.conv_res(x) + if self.downsample: + x = self.net_downsample(x) + res = nn.functional.interpolate(res, scale_factor=0.5) + else: + x = self.net(x) + x = (x + res) * (1 / math.sqrt(2)) + x = self.act(x) + return x + +class VectorAndPatchDiscriminatorIN(nn.Module): + def __init__(self, in_channels=1, width=64, vec_size=2048): + super().__init__() + self.model = nn.Sequential( + ResBlockIN(in_channels, width, width, downsample=True), #128 / 32x256 + ResBlockIN(width, 2*width, 2*width, downsample=True), #64 / 16x128 + ResBlockIN(2*width, 4*width, 4*width, downsample=True), #32 / 8x64 + ResBlockIN(4*width, 8*width, 8*width, downsample=True), #16 / 4x32 + nn.ZeroPad2d((1, 0, 1, 0)) + ) + self.vector_head = nn.Sequential( + nn.Conv2d(8*width, width, 4, padding=1, bias=False), + nn.InstanceNorm2d(width), + nn.GELU(), + nn.Flatten(), + nn.LazyLinear(vec_size) + ) + self.realfake_head = nn.Conv2d(8*width, 1, 4, padding=1, bias=False) + + def forward(self, img): + # Concatenate image and condition image by channels to produce input + feat = self.model(img) + return self.vector_head(feat), self.realfake_head(feat) + +class fclayer(nn.Module): + def __init__(self, in_h = 4, in_w = 32, out_n = 2048): + super().__init__() + self.in_h = in_h + self.in_w = in_w + self.out_n = out_n + self.fc_list = [] + for i in range(out_n): + self.fc_list.append(nn.Linear(in_h*in_w, 1)) + self.fc_list = nn.ModuleList(self.fc_list) + def forward(self, x): + x = x.reshape(-1, self.out_n, self.in_h, self.in_w) + outs = [] + for i in range(self.out_n): + outs.append(self.fc_list[i](x[:, i, :, :].reshape(-1, self.in_h*self.in_w))) + out = torch.cat(outs, 1) + return out + +class VectorAndPatchDiscriminatorINv2(nn.Module): + def __init__(self, in_channels=1, width=128, vec_size=2048): + super().__init__() + self.model = nn.Sequential( + ResBlockIN(in_channels, width, width, downsample=True), # 32x256 + ResBlockIN(width, 2*width, 2*width, downsample=True), # 16x128 + ResBlockIN(2*width, 4*width, 4*width, downsample=True), # 8x64 + ResBlockIN(4*width, 8*width, 8*width, downsample=True) # 4x32 + ) + self.vector_head = nn.Sequential( + ResBlockIN(8*width, vec_size, vec_size), + fclayer(in_h=4, in_w=32, out_n=vec_size) + ) + self.realfake_head = nn.Sequential( + nn.ZeroPad2d((1, 0, 1, 0)), + nn.Conv2d(8*width, 1, 4, padding=1, bias=False) + ) + + def forward(self, img): + # Concatenate image and condition image by channels to produce input + feat = self.model(img) + return self.vector_head(feat), self.realfake_head(feat) + +class AttBlockIN(nn.Module): + def __init__(self,F_g,F_l,F_int): + super(AttBlockIN,self).__init__() + self.W_g = nn.Sequential( + nn.Conv2d(F_g, F_int, kernel_size=1,stride=1,padding=0,bias=True), + nn.InstanceNorm2d(F_int) + ) + + self.W_x = nn.Sequential( + nn.Conv2d(F_l, F_int, kernel_size=1,stride=1,padding=0,bias=True), + nn.InstanceNorm2d(F_int) + ) + + self.psi = nn.Sequential( + nn.Conv2d(F_int, 1, kernel_size=1,stride=1,padding=0,bias=True), + nn.InstanceNorm2d(1), + nn.Sigmoid() + ) + + self.act = nn.GELU() + + def forward(self,g,x): + g1 = self.W_g(g) + x1 = self.W_x(x) + psi = self.act(g1+x1) + psi = self.psi(psi) + + return x*psi + +class NestedAttentionResUNetIN(nn.Module): + def __init__(self, num_classes, num_channels, width=32, resolution=(256, 256)): + super().__init__() + self.resolution = resolution + nb_filter = [width, width*2, width*4, width*8, width*16] + + self.pool = nn.PixelUnshuffle(2) + self.up = nn.Upsample(scale_factor=2, mode='bilinear') + + self.conv0_0 = ResBlockIN(num_channels, nb_filter[0], nb_filter[0]) + self.conv1_0 = ResBlockIN(nb_filter[0]*4, nb_filter[1], nb_filter[1]) + self.conv2_0 = ResBlockIN(nb_filter[1]*4, nb_filter[2], nb_filter[2]) + self.conv3_0 = ResBlockIN(nb_filter[2]*4, nb_filter[3], nb_filter[3]) + self.conv4_0 = ResBlockIN(nb_filter[3]*4, nb_filter[4], nb_filter[4]) + + self.conv0_1 = ResBlockIN(nb_filter[0]+nb_filter[1], nb_filter[0], nb_filter[0]) + self.att0_1 = AttBlockIN(nb_filter[1], nb_filter[0], nb_filter[0]) + self.conv1_1 = ResBlockIN(nb_filter[1]+nb_filter[2], nb_filter[1], nb_filter[1]) + self.att1_1 = AttBlockIN(nb_filter[2], nb_filter[1], nb_filter[1]) + self.conv2_1 = ResBlockIN(nb_filter[2]+nb_filter[3], nb_filter[2], nb_filter[2]) + self.att2_1 = AttBlockIN(nb_filter[3], nb_filter[2], nb_filter[2]) + self.conv3_1 = ResBlockIN(nb_filter[3]+nb_filter[4], nb_filter[3], nb_filter[3]) + self.att3_1 = AttBlockIN(nb_filter[4], nb_filter[3], nb_filter[3]) + + self.conv0_2 = ResBlockIN(nb_filter[0]*2+nb_filter[1], nb_filter[0], nb_filter[0]) + self.att0_2 = AttBlockIN(nb_filter[1], nb_filter[0]*2, nb_filter[0]) + self.conv1_2 = ResBlockIN(nb_filter[1]*2+nb_filter[2], nb_filter[1], nb_filter[1]) + self.att1_2 = AttBlockIN(nb_filter[2], nb_filter[1]*2, nb_filter[1]) + self.conv2_2 = ResBlockIN(nb_filter[2]*2+nb_filter[3], nb_filter[2], nb_filter[2]) + self.att2_2 = AttBlockIN(nb_filter[3], nb_filter[2]*2, nb_filter[2]) + + self.conv0_3 = ResBlockIN(nb_filter[0]*3+nb_filter[1], nb_filter[0], nb_filter[0]) + self.att0_3 = AttBlockIN(nb_filter[1], nb_filter[0]*3, nb_filter[0]) + self.conv1_3 = ResBlockIN(nb_filter[1]*3+nb_filter[2], nb_filter[1], nb_filter[1]) + self.att1_3 = AttBlockIN(nb_filter[2], nb_filter[1]*3, nb_filter[1]) + + self.conv0_4 = ResBlockIN(nb_filter[0]*4+nb_filter[1], nb_filter[0], nb_filter[0]) + self.att0_4 = AttBlockIN(nb_filter[1], nb_filter[0]*4, nb_filter[0]) + + self.final = nn.Conv2d(nb_filter[0]*4, num_classes, kernel_size=1) + + def forward(self, input): + + x0_0 = self.conv0_0(input) #320x240 + x1_0 = self.conv1_0(self.pool(x0_0)) #160x120 + x0_0 = self.att0_1(g=self.up(x1_0), x=x0_0) + x0_1 = self.conv0_1(torch.cat([x0_0, self.up(x1_0)], 1)) + + x2_0 = self.conv2_0(self.pool(x1_0)) #80x60 + + x1_0 = self.att1_1(g=self.up(x2_0), x=x1_0) + x1_1 = self.conv1_1(torch.cat([x1_0, self.up(x2_0)], 1)) + + x0_01 = self.att0_2(g=self.up(x1_1), x=torch.cat([x0_0, x0_1], 1)) + x0_2 = self.conv0_2(torch.cat([x0_01, self.up(x1_1)], 1)) + + x3_0 = self.conv3_0(self.pool(x2_0)) #40x30 + x2_0 = self.att2_1(g=self.up(x3_0), x=x2_0) + x2_1 = self.conv2_1(torch.cat([x2_0, self.up(x3_0)], 1)) + x1_01 = self.att1_2(g=self.up(x2_1), x=torch.cat([x1_0, x1_1], 1)) + x1_2 = self.conv1_2(torch.cat([x1_01, self.up(x2_1)], 1)) + x0_012 = self.att0_3(g=self.up(x1_2), x=torch.cat([x0_0, x0_1, x0_2], 1)) + x0_3 = self.conv0_3(torch.cat([x0_012, self.up(x1_2)], 1)) + + x4_0 = self.conv4_0(self.pool(x3_0)) #20x15 + x3_0 = self.att3_1(g=self.up(x4_0), x=x3_0) + x3_1 = self.conv3_1(torch.cat([x3_0, self.up(x4_0)], 1)) + x2_01 = self.att2_2(g=self.up(x3_1), x=torch.cat([x2_0, x2_1], 1)) + x2_2 = self.conv2_2(torch.cat([x2_01, self.up(x3_1)], 1)) + x1_012 = self.att1_3(g=self.up(x2_2), x=torch.cat([x1_0, x1_1, x1_2], 1)) + x1_3 = self.conv1_3(torch.cat([x1_012, self.up(x2_2)], 1)) + x0_0123 = self.att0_4(g=self.up(x1_3), x=torch.cat([x0_0, x0_1, x0_2, x0_3], 1)) + x0_4 = self.conv0_4(torch.cat([x0_0123, self.up(x1_3)], 1)) + + output = self.final(torch.cat([x0_1, x0_2, x0_3, x0_4], 1)) + + return output + +class SharedAtrousConv2d(nn.Module): + def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, bias=True, padding='same'): + super().__init__() + self.weights = nn.Parameter(torch.rand(int(out_channels/2), in_channels, kernel_size, kernel_size)) + self.stride = stride + self.kernel_size = kernel_size + self.padding = padding + nn.init.xavier_uniform_(self.weights) + if bias: + self.bias1 = nn.Parameter(torch.zeros(int(out_channels/2))) + self.bias2 = nn.Parameter(torch.zeros(int(out_channels/2))) + else: + self.bias1 = None + self.bias2 = None + def forward(self, x): + b, c, h, w = x.shape + + if self.padding == 'same': + pad_val1_h = int(((h - 1)*self.stride - h + self.kernel_size) / 2) + pad_val1_w = int(((w - 1)*self.stride - w + self.kernel_size) / 2) + pad_val2_h = int(((h - 1)*self.stride - h + 2 * self.kernel_size - 1) / 2) + pad_val2_w = int(((w - 1)*self.stride - w + 2 * self.kernel_size - 1) / 2) + elif self.padding == 'valid': + pad_val1_h = 0 + pad_val1_w = 0 + pad_val2_h = 0 + pad_val2_w = 0 + + x1 = nn.functional.conv2d(x, self.weights, stride=self.stride, padding=(pad_val1_h, pad_val1_w), bias=self.bias1) + x2 = nn.functional.conv2d(x, self.weights, stride=self.stride, padding=(pad_val2_h, pad_val2_w), dilation=2, bias=self.bias2) + x3 = torch.cat([x1, x2], 1) + return x3 + +class SharedAtrousResBlock(nn.Module): + def __init__(self, in_channels, middle_channels, out_channels): + super().__init__() + self.conv_res = nn.Sequential( + nn.Conv2d(in_channels, out_channels, 1, stride=1, bias=False), + nn.BatchNorm2d(out_channels) + ) + self.net = nn.Sequential( + SharedAtrousConv2d(in_channels, middle_channels, bias=False), + nn.BatchNorm2d(middle_channels), + nn.ReLU(inplace=True), + SharedAtrousConv2d(middle_channels, out_channels, bias=False), + nn.BatchNorm2d(out_channels) + ) + self.relu = nn.ReLU(inplace=True) + def forward(self, x): + res = self.conv_res(x) + x = self.net(x) + x = (x + res) * (1 / math.sqrt(2)) + x = self.relu(x) + return x + +class Resize(nn.Module): + def __init__(self, size=None, scale_factor=None, mode='nearest', align_corners=None, recompute_scale_factor=None, antialias=False): + super().__init__() + self.size = size + self.scale_factor = scale_factor + self.mode = mode + self.align_corners = align_corners + self.recompute_scale_factor = recompute_scale_factor + self.antialias = antialias + def forward(self, x): + return torch.nn.functional.interpolate(x, size=self.size, scale_factor=self.scale_factor, mode=self.mode, align_corners=self.align_corners, recompute_scale_factor=self.recompute_scale_factor, antialias=self.antialias) + +class NestedSharedAtrousResUNet(nn.Module): + def __init__(self, num_classes, num_channels, width=32, resolution=(240, 320)): + super().__init__() + self.resolution = resolution + nb_filter = [width, width*2, width*4, width*8, width*16] + + self.pool = Resize(scale_factor=0.5, mode='bilinear') + self.up = Resize(scale_factor=2, mode='bilinear') + + self.conv0_0 = SharedAtrousResBlock(num_channels, nb_filter[0], nb_filter[0]) + self.conv1_0 = SharedAtrousResBlock(nb_filter[0], nb_filter[1], nb_filter[1]) + self.conv2_0 = SharedAtrousResBlock(nb_filter[1], nb_filter[2], nb_filter[2]) + self.conv3_0 = SharedAtrousResBlock(nb_filter[2], nb_filter[3], nb_filter[3]) + self.conv4_0 = SharedAtrousResBlock(nb_filter[3], nb_filter[4], nb_filter[4]) + + self.conv0_1 = SharedAtrousResBlock(nb_filter[0]+nb_filter[1], nb_filter[0], nb_filter[0]) + self.conv1_1 = SharedAtrousResBlock(nb_filter[1]+nb_filter[2], nb_filter[1], nb_filter[1]) + self.conv2_1 = SharedAtrousResBlock(nb_filter[2]+nb_filter[3], nb_filter[2], nb_filter[2]) + self.conv3_1 = SharedAtrousResBlock(nb_filter[3]+nb_filter[4], nb_filter[3], nb_filter[3]) + + self.conv0_2 = SharedAtrousResBlock(nb_filter[0]*2+nb_filter[1], nb_filter[0], nb_filter[0]) + self.conv1_2 = SharedAtrousResBlock(nb_filter[1]*2+nb_filter[2], nb_filter[1], nb_filter[1]) + self.conv2_2 = SharedAtrousResBlock(nb_filter[2]*2+nb_filter[3], nb_filter[2], nb_filter[2]) + + self.conv0_3 = SharedAtrousResBlock(nb_filter[0]*3+nb_filter[1], nb_filter[0], nb_filter[0]) + self.conv1_3 = SharedAtrousResBlock(nb_filter[1]*3+nb_filter[2], nb_filter[1], nb_filter[1]) + + self.conv0_4 = SharedAtrousResBlock(nb_filter[0]*4+nb_filter[1], nb_filter[0], nb_filter[0]) + + self.final = nn.Conv2d(nb_filter[0]*4, num_classes, kernel_size=1) + + def forward(self, input): + + x0_0 = self.conv0_0(input) #320x240 + x1_0 = self.conv1_0(self.pool(x0_0)) #160x120 + x0_1 = self.conv0_1(torch.cat([x0_0, self.up(x1_0)], 1)) + + x2_0 = self.conv2_0(self.pool(x1_0)) #80x60 + x1_1 = self.conv1_1(torch.cat([x1_0, self.up(x2_0)], 1)) + x0_2 = self.conv0_2(torch.cat([x0_0, x0_1, self.up(x1_1)], 1)) + + x3_0 = self.conv3_0(self.pool(x2_0)) #40x30 + x2_1 = self.conv2_1(torch.cat([x2_0, self.up(x3_0)], 1)) + x1_2 = self.conv1_2(torch.cat([x1_0, x1_1, self.up(x2_1)], 1)) + x0_3 = self.conv0_3(torch.cat([x0_0, x0_1, x0_2, self.up(x1_2)], 1)) + + x4_0 = self.conv4_0(self.pool(x3_0)) #20x15 + x3_1 = self.conv3_1(torch.cat([x3_0, self.up(x4_0)], 1)) + x2_2 = self.conv2_2(torch.cat([x2_0, x2_1, self.up(x3_1)], 1)) + x1_3 = self.conv1_3(torch.cat([x1_0, x1_1, x1_2, self.up(x2_2)], 1)) + x0_4 = self.conv0_4(torch.cat([x0_0, x0_1, x0_2, x0_3, self.up(x1_3)], 1)) + + output = self.final(torch.cat([x0_1, x0_2, x0_3, x0_4], 1)) + + return output + +class SharedAtrousResBlockIN(nn.Module): + def __init__(self, in_channels, middle_channels, out_channels, downsample=False): + super(SharedAtrousResBlockIN,self).__init__() + self.conv_res = nn.Sequential( + nn.Conv2d(in_channels, out_channels, 1, stride=1, padding='same', bias=False), + nn.InstanceNorm2d(out_channels) + ) + self.downsample = downsample + if downsample: + self.net_downsample = nn.Sequential( + SharedAtrousConv2d(in_channels, middle_channels, kernel_size=4, stride=2, bias=False), + nn.InstanceNorm2d(middle_channels), + nn.GELU(), + SharedAtrousConv2d(middle_channels, out_channels, kernel_size=3, stride=1, bias=False), + nn.InstanceNorm2d(out_channels) + ) + else: + self.net = nn.Sequential( + SharedAtrousConv2d(in_channels, middle_channels, kernel_size=3, stride=1, bias=False), + nn.InstanceNorm2d(middle_channels), + nn.GELU(), + SharedAtrousConv2d(middle_channels, out_channels, kernel_size=3, stride=1, bias=False), + nn.InstanceNorm2d(out_channels) + ) + self.act = nn.GELU() + def forward(self, x): + res = self.conv_res(x) + if self.downsample: + x = self.net_downsample(x) + res = nn.functional.interpolate(res, scale_factor=0.5) + else: + x = self.net(x) + x = (x + res) * (1 / math.sqrt(2)) + x = self.act(x) + return x + +class AttBlockIN(nn.Module): + def __init__(self,F_g,F_l,F_int): + super(AttBlockIN,self).__init__() + self.W_g = nn.Sequential( + nn.Conv2d(F_g, F_int, kernel_size=1,stride=1,padding=0,bias=True), + nn.InstanceNorm2d(F_int) + ) + + self.W_x = nn.Sequential( + nn.Conv2d(F_l, F_int, kernel_size=1,stride=1,padding=0,bias=True), + nn.InstanceNorm2d(F_int) + ) + + self.psi = nn.Sequential( + nn.Conv2d(F_int, 1, kernel_size=1,stride=1,padding=0,bias=True), + nn.InstanceNorm2d(1), + nn.Sigmoid() + ) + + self.act = nn.GELU() + + def forward(self,g,x): + g1 = self.W_g(g) + x1 = self.W_x(x) + psi = self.act(g1+x1) + psi = self.psi(psi) + + return x*psi + +class NestedSharedAtrousAttentionResUNetIN(nn.Module): + def __init__(self, num_classes, num_channels, width=32, resolution=(240, 320)): + super().__init__() + self.resolution = resolution + nb_filter = [width, width*2, width*4, width*8, width*16] + + self.pool = Resize(scale_factor=0.5, mode='bilinear') + self.up = Resize(scale_factor=2, mode='bilinear') + + self.conv0_0 = SharedAtrousResBlockIN(num_channels, nb_filter[0], nb_filter[0]) + self.conv1_0 = SharedAtrousResBlockIN(nb_filter[0], nb_filter[1], nb_filter[1]) + self.conv2_0 = SharedAtrousResBlockIN(nb_filter[1], nb_filter[2], nb_filter[2]) + self.conv3_0 = SharedAtrousResBlockIN(nb_filter[2], nb_filter[3], nb_filter[3]) + self.conv4_0 = SharedAtrousResBlockIN(nb_filter[3], nb_filter[4], nb_filter[4]) + + self.conv0_1 = SharedAtrousResBlockIN(nb_filter[0]+nb_filter[1], nb_filter[0], nb_filter[0]) + self.att0_1 = AttBlockIN(nb_filter[1], nb_filter[0], nb_filter[0]) + self.conv1_1 = SharedAtrousResBlockIN(nb_filter[1]+nb_filter[2], nb_filter[1], nb_filter[1]) + self.att1_1 = AttBlockIN(nb_filter[2], nb_filter[1], nb_filter[1]) + self.conv2_1 = SharedAtrousResBlockIN(nb_filter[2]+nb_filter[3], nb_filter[2], nb_filter[2]) + self.att2_1 = AttBlockIN(nb_filter[3], nb_filter[2], nb_filter[2]) + self.conv3_1 = SharedAtrousResBlockIN(nb_filter[3]+nb_filter[4], nb_filter[3], nb_filter[3]) + self.att3_1 = AttBlockIN(nb_filter[4], nb_filter[3], nb_filter[3]) + + self.conv0_2 = SharedAtrousResBlockIN(nb_filter[0]*2+nb_filter[1], nb_filter[0], nb_filter[0]) + self.att0_2 = AttBlockIN(nb_filter[1], nb_filter[0]*2, nb_filter[0]) + self.conv1_2 = SharedAtrousResBlockIN(nb_filter[1]*2+nb_filter[2], nb_filter[1], nb_filter[1]) + self.att1_2 = AttBlockIN(nb_filter[2], nb_filter[1]*2, nb_filter[1]) + self.conv2_2 = ResBlockIN(nb_filter[2]*2+nb_filter[3], nb_filter[2], nb_filter[2]) + self.att2_2 = AttBlockIN(nb_filter[3], nb_filter[2]*2, nb_filter[2]) + + self.conv0_3 = SharedAtrousResBlockIN(nb_filter[0]*3+nb_filter[1], nb_filter[0], nb_filter[0]) + self.att0_3 = AttBlockIN(nb_filter[1], nb_filter[0]*3, nb_filter[0]) + self.conv1_3 = SharedAtrousResBlockIN(nb_filter[1]*3+nb_filter[2], nb_filter[1], nb_filter[1]) + self.att1_3 = AttBlockIN(nb_filter[2], nb_filter[1]*3, nb_filter[1]) + + self.conv0_4 = SharedAtrousResBlockIN(nb_filter[0]*4+nb_filter[1], nb_filter[0], nb_filter[0]) + self.att0_4 = AttBlockIN(nb_filter[1], nb_filter[0]*4, nb_filter[0]) + + self.final = nn.Conv2d(nb_filter[0]*5, num_classes, kernel_size=1) + + def forward(self, input): + + x0_0 = self.conv0_0(input) #320x240 + x1_0 = self.conv1_0(self.pool(x0_0)) #160x120 + x0_0 = self.att0_1(g=self.up(x1_0), x=x0_0) + x0_1 = self.conv0_1(torch.cat([x0_0, self.up(x1_0)], 1)) + + x2_0 = self.conv2_0(self.pool(x1_0)) #80x60 + + x1_0 = self.att1_1(g=self.up(x2_0), x=x1_0) + x1_1 = self.conv1_1(torch.cat([x1_0, self.up(x2_0)], 1)) + + x0_01 = self.att0_2(g=self.up(x1_1), x=torch.cat([x0_0, x0_1], 1)) + x0_2 = self.conv0_2(torch.cat([x0_01, self.up(x1_1)], 1)) + + x3_0 = self.conv3_0(self.pool(x2_0)) #40x30 + x2_0 = self.att2_1(g=self.up(x3_0), x=x2_0) + x2_1 = self.conv2_1(torch.cat([x2_0, self.up(x3_0)], 1)) + x1_01 = self.att1_2(g=self.up(x2_1), x=torch.cat([x1_0, x1_1], 1)) + x1_2 = self.conv1_2(torch.cat([x1_01, self.up(x2_1)], 1)) + x0_012 = self.att0_3(g=self.up(x1_2), x=torch.cat([x0_0, x0_1, x0_2], 1)) + x0_3 = self.conv0_3(torch.cat([x0_012, self.up(x1_2)], 1)) + + x4_0 = self.conv4_0(self.pool(x3_0)) #20x15 + x3_0 = self.att3_1(g=self.up(x4_0), x=x3_0) + x3_1 = self.conv3_1(torch.cat([x3_0, self.up(x4_0)], 1)) + x2_01 = self.att2_2(g=self.up(x3_1), x=torch.cat([x2_0, x2_1], 1)) + x2_2 = self.conv2_2(torch.cat([x2_01, self.up(x3_1)], 1)) + x1_012 = self.att1_3(g=self.up(x2_2), x=torch.cat([x1_0, x1_1, x1_2], 1)) + x1_3 = self.conv1_3(torch.cat([x1_012, self.up(x2_2)], 1)) + x0_0123 = self.att0_4(g=self.up(x1_3), x=torch.cat([x0_0, x0_1, x0_2, x0_3], 1)) + x0_4 = self.conv0_4(torch.cat([x0_0123, self.up(x1_3)], 1)) + + output = self.final(torch.cat([x0_0, x0_1, x0_2, x0_3, x0_4], 1)) + + return output \ No newline at end of file diff --git a/pdm/requirements.txt b/pdm/requirements.txt new file mode 100644 index 0000000..511da91 --- /dev/null +++ b/pdm/requirements.txt @@ -0,0 +1,49 @@ +accelerate==1.1.1 +astroid==3.3.5 +Brotli==1.0.9 +certifi==2024.8.30 +charset-normalizer==3.3.2 +dill==0.3.9 +docopt==0.6.2 +filelock==3.13.1 +fsspec==2024.10.0 +huggingface-hub==0.26.3 +idna==3.7 +isort==5.13.2 +Jinja2==3.1.4 +joblib==1.4.2 +kornia==0.7.4 +kornia_rs==0.1.7 +MarkupSafe==2.1.3 +mccabe==0.7.0 +mpmath==1.3.0 +networkx==3.2.1 +numpy==1.26.4 +opencv-python==4.10.0.84 +opencv-python-headless==4.10.0.84 +packaging==24.2 +pillow==11.0.0 +pip==24.2 +platformdirs==4.3.6 +protobuf==7.35.1 +psutil==6.1.0 +pylint==3.3.2 +PySocks==1.7.1 +PyYAML==6.0.2 +requests==2.32.3 +safetensors==0.4.5 +scikit-learn==1.5.1 +scipy==1.14.1 +setuptools==72.1.0 +stomp.py==8.2.0 +sympy==1.13.1 +threadpoolctl==3.5.0 +tomlkit==0.13.2 +torch==2.5.1 +torchaudio==2.5.1 +torchvision==0.20.1 +tqdm==4.67.1 +typing_extensions==4.11.0 +urllib3==2.2.3 +websocket-client==1.8.0 +wheel==0.44.0 diff --git a/pdm/resources/Image.png b/pdm/resources/Image.png new file mode 100644 index 0000000..b85e925 Binary files /dev/null and b/pdm/resources/Image.png differ diff --git a/pdm/test.py b/pdm/test.py new file mode 100644 index 0000000..3330bb1 --- /dev/null +++ b/pdm/test.py @@ -0,0 +1,125 @@ +import stomp +import Iwp_pb2 +import Pdm_pb2 +from PIL import Image +import io +import os +from typing import Tuple + +PDM_REQUEST_QUEUE = "iris.pdm.request" +DUAL_PDM_REQUEST_QUEUE = "iris.dual.pdm.request" +ARTEMIS_ANYCAST_HEADERS = {"destination-type": "ANYCAST"} + +def stomp_host() -> Tuple[str, int]: + host_and_port = os.getenv("STOMP_HOST") + if host_and_port: + if ":" in host_and_port: + host, port = host_and_port.rsplit(":", 1) + else: + host, port = host_and_port, "61613" + else: + host = os.getenv("ACTIVEMQ_HOST") + port = os.getenv("ACTIVEMQ_PORT") + + return host or "activemq", int(port) + +def artemis_credentials() -> Tuple[str, str]: + username = os.getenv("ARTEMIS_USER") + password = os.getenv("ARTEMIS_PASSWORD") + + if not username or not password: + raise RuntimeError(f"Missing required Artemis credentials") + + return username, password + +def connect_to_broker(auto_decode=True): + conn = stomp.Connection([stomp_host()], auto_decode=auto_decode) + username, password = artemis_credentials() + conn.connect(username, password, wait=True) + return conn + + +def send_single_pdm_test_message(): + # Prepare the image data + image_path = "resources/Image.png" + with open(image_path, "rb") as f: + image_bytes = f.read() + + # Load the image to extract metadata + image = Image.open(io.BytesIO(image_bytes)) + width, height = image.size + + # Create the Protobuf message + image_service_query = Iwp_pb2.ImageServiceQuery() + image_service_query.imageId = "test_image_id" + image_service_query.imageBytes = image_bytes + image_service_query.width = width + image_service_query.height = height + image_service_query.imageType = Iwp_pb2.ImageServiceQuery.PNG # Assuming PNG format + + # Serialize the Protobuf message into a byte array + serialized_message = image_service_query.SerializeToString() + print( + "Serialized Protobuf Message:", + type(serialized_message), + len(serialized_message), + ) + + # Connect to ActiveMQ Artemis and send the message as raw bytes + conn = connect_to_broker() + conn.send( + destination=PDM_REQUEST_QUEUE, + body=serialized_message, + headers={ + **ARTEMIS_ANYCAST_HEADERS, + "content-type": "application/octet-stream", + }, + ) + conn.disconnect() + + print("Test message sent successfully") + + +def send_dual_pdm_test_message(): + # Prepare the image data + probe_path = "resources/Image.png" + cand_path = "resources/Image.png" + + with open(probe_path, "rb") as f: + probe_bytes = f.read() + + with open(cand_path, "rb") as f: + candidate_bytes = f.read() + + # Create the Protobuf message + query = Pdm_pb2.DualPDMComparison() + query.probe = probe_bytes + query.candidate = candidate_bytes + + # Serialize + serialized_message = query.SerializeToString() + print( + "Serialized Protobuf Message:", + type(serialized_message), + len(serialized_message), + ) + + + # Connect to ActiveMQ Artemis and send the message as raw bytes + conn = connect_to_broker() + conn.send( + destination=DUAL_PDM_REQUEST_QUEUE, + body=serialized_message, + headers={ + **ARTEMIS_ANYCAST_HEADERS, + "content-type": "application/octet-stream", + }, + ) + conn.disconnect() + + print("Test message sent successfully") + + +#### Test PDM Capabilities #### +send_single_pdm_test_message() +send_dual_pdm_test_message() diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..422700f --- /dev/null +++ b/pom.xml @@ -0,0 +1,26 @@ + + + + 4.0.0 + org.mitre + iris-workstation + 1.0 + pom + + + buffers + biqt + common + support + web + + diff --git a/scripts/build-all.sh b/scripts/build-all.sh new file mode 100755 index 0000000..049a224 --- /dev/null +++ b/scripts/build-all.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government and +# is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) - Alternative IV (Dec 2007). +# +# (c) 2026 The MITRE Corporation. All Rights Reserved. +# + +set -euo pipefail + +IAT_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +DEPENDENCY_ROOT=${IAT_DEPENDENCY_ROOT:-"$IAT_ROOT/../iat-dependencies"} +PLATFORM=${IAT_PLATFORM:-linux/amd64} +CA_FILE=${IAT_CA_FILE:-} + +WEB_IMAGE=${IAT_WEB_IMAGE:-ghcr.io/mitre/iat/iwp-web:26.08} +ACII_IMAGE=${IAT_ACII_IMAGE:-ghcr.io/mitre/iat/iwp-acii:26.08} +BIQT_IMAGE=${IAT_BIQT_IMAGE:-ghcr.io/mitre/iat/iwp-biqt:26.08} +ANNOTATION_IMAGE=${IAT_ANNOTATION_IMAGE:-ghcr.io/mitre/iat/iwp-annotation:26.08} +TSHEPII_IMAGE=${IAT_TSHEPII_IMAGE:-ghcr.io/mitre/iat/iwp-tshepii:26.08} +PDM_IMAGE=${IAT_PDM_IMAGE:-ghcr.io/mitre/iat/iwp-pdm:26.08} + +require_command() { + command -v "$1" >/dev/null 2>&1 || { + echo "Required command not found: $1" >&2 + exit 1 + } +} + +clone_if_missing() { + local repository=$1 + local directory=$2 + + if [ ! -d "$directory/.git" ]; then + git clone "$repository" "$directory" + fi +} + +build_image() { + local image=$1 + local dockerfile=$2 + local context=$3 + local -a build_args + + build_args=(--platform "$PLATFORM" -t "$image" -f "$dockerfile") + + if [ -n "$CA_FILE" ]; then + if [ ! -f "$CA_FILE" ]; then + echo "IAT_CA_FILE does not identify a readable certificate file: $CA_FILE" >&2 + exit 1 + fi + build_args+=(--secret "id=ca_file,src=$CA_FILE") + fi + + DOCKER_BUILDKIT=1 docker build "${build_args[@]}" "$context" +} + +require_command docker +require_command git +require_command mvn + +mkdir -p "$DEPENDENCY_ROOT" +clone_if_missing https://github.com/ebts/jet.git "$DEPENDENCY_ROOT/jet" +clone_if_missing https://github.com/mitre/biqt.git "$DEPENDENCY_ROOT/biqt" + +( + cd "$DEPENDENCY_ROOT/jet/Jet" + mvn install +) + +( + cd "$DEPENDENCY_ROOT/biqt/java" + # BIQT's Java tests require the separately built libbiqtapi native library. + # IAT needs the Java artifact, so install it without running those upstream tests. + mvn install -DskipTests +) + +( + cd "$IAT_ROOT" + mvn -DskipTests package +) + +( + cd "$IAT_ROOT/pdm" + ./download-models.sh +) + +build_image "$ACII_IMAGE" "$IAT_ROOT/acii/Dockerfile" "$IAT_ROOT" +build_image "$BIQT_IMAGE" "$IAT_ROOT/biqt/Dockerfile" "$IAT_ROOT/biqt" +build_image "$ANNOTATION_IMAGE" "$IAT_ROOT/inference/core/Dockerfile-CPU" "$IAT_ROOT/inference/core" +build_image "$TSHEPII_IMAGE" "$IAT_ROOT/tshepii/Dockerfile" "$IAT_ROOT" +build_image "$PDM_IMAGE" "$IAT_ROOT/pdm/Dockerfile" "$IAT_ROOT/pdm" +build_image "$WEB_IMAGE" "$IAT_ROOT/web/Dockerfile" "$IAT_ROOT/web" + +echo "All IAT artifacts and CPU Docker images were built successfully." diff --git a/support/README.md b/support/README.md new file mode 100644 index 0000000..741f987 --- /dev/null +++ b/support/README.md @@ -0,0 +1,61 @@ +> +> NOTICE +> +> This software (or technical data) was produced for the U. S. Government and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV (May 2014) - Alternative IV (Dec 2007). +> +> (c) 2024 The MITRE Corporation. All Rights Reserved. +> + +# Support Module + +The Support module is an Iris Analysis Toolkit (IAT) Java library used by the Web component to create default EBTS transactions. It also provides an SRB transaction creator for generating sample transaction files. + +This module is not a standalone service and does not require Docker or ActiveMQ Artemis to build. + +## Building the Module +### Prerequisites +- Java 17. +- Maven. +- The IAT JET dependency installed in the local Maven repository. From the main `iat/` directory, clone and build it with: + ```sh + git clone https://github.com/ebts/jet.git jet + cd jet/Jet + mvn install + cd ../.. + ``` + +### Build JAR +From the main `iat/` directory, run: +```sh +mvn -pl support -am package +``` + +The built JAR is written to `support/target/`. + +## Using the Module + +The Web component uses `EbtsTransactionCreator` to generate a default EBTS transaction when an EBTS message is requested without custom transaction data. `SrbTransactionCreator` can generate a sample SRB transaction. + +The default transaction values are configured in: + +```text +src/main/resources/TransactionCreatorEbts.properties +src/main/resources/TransactionCreatorSrb.properties +``` + +## Change the Default Iris Image + +The transaction-creator properties require a default iris image. A sample +image is provided at `src/main/resources/Image.png`. To use an image of your +choice, use one of the following options: + +1. Replace `src/main/resources/Image.png`, keeping the filename and location + unchanged. +2. Add the image under `src/main/resources/` and update the image property in + the transaction-creator properties file. + + - `TransactionCreatorEbts.properties` uses `17.999`. + - `TransactionCreatorSrb.properties` uses `17.999_1`, `17.999_2`, and + `17.999_3`. + +Rebuild the module after changing an image or transaction property. diff --git a/support/pom.xml b/support/pom.xml new file mode 100644 index 0000000..dd7865d --- /dev/null +++ b/support/pom.xml @@ -0,0 +1,55 @@ + + + + 4.0.0 + + org.mitre + support + 1.0.0 + + 17 + + + + org.mitre.jet + jet + 1.0.0 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-site-plugin + + true + + + + + diff --git a/support/src/main/java/org/mitre/iwp/support/EbtsTransactionCreator.java b/support/src/main/java/org/mitre/iwp/support/EbtsTransactionCreator.java new file mode 100644 index 0000000..5dfa79f --- /dev/null +++ b/support/src/main/java/org/mitre/iwp/support/EbtsTransactionCreator.java @@ -0,0 +1,160 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.support; + +import com.google.common.io.Files; +import org.mitre.jet.ebts.Ebts; +import org.mitre.jet.ebts.EbtsBuilder; +import org.mitre.jet.ebts.field.Field; +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.mitre.jet.ebts.records.GenericRecord; +import org.mitre.jet.ebts.records.LogicalRecord; +import org.mitre.jet.exceptions.EbtsBuildingException; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.Properties; + +public class EbtsTransactionCreator { + public static void main(String[] args) throws IOException, EbtsBuildingException { + new EbtsTransactionCreator().create(); + } + + public Ebts createDefault() throws IOException { + Ebts ebts = new Ebts(); + Properties properties = new Properties(); + properties.load(ClassLoader.getSystemResourceAsStream("TransactionCreatorEbts.properties")); + + // Type-1 + LogicalRecord type1 = new GenericRecord(1); + type1.getFields().put(2, toField(properties.getProperty("1.2"))); + type1.getFields().put(4, toField(properties.getProperty("1.4"))); + type1.getFields().put(5, toField(properties.getProperty("1.5"))); + type1.getFields().put(7, toField(properties.getProperty("1.7"))); + type1.getFields().put(8, toField(properties.getProperty("1.8"))); + type1.getFields().put(9, toField(properties.getProperty("1.9"))); + type1.getFields().put(10, toField(properties.getProperty("1.10"))); + type1.getFields().put(11, toField(properties.getProperty("1.11"))); + type1.getFields().put(12, toField(properties.getProperty("1.12"))); + type1.getFields().put(13, toField(properties.getProperty("1.13A"), + properties.getProperty("1.13B"))); + type1.getFields().put(16, toField(properties.getProperty("1.16A"), + properties.getProperty("1.16B"), + properties.getProperty("1.16C"))); + + // Type-2 + LogicalRecord type2 = new GenericRecord(2); + type2.getFields().put(6, toField(properties.getProperty("2.6"))); + type2.getFields().put(10, toField(properties.getProperty("2.10A"), + properties.getProperty("2.10B"))); + type2.getFields().put(11, toField(properties.getProperty("2.11"))); + + // Type-17 + BufferedImage image = ImageIO.read(ClassLoader.getSystemResourceAsStream(properties.getProperty("17.999"))); + LogicalRecord type17 = new GenericRecord(17); + type17.getFields().put(3, toField(properties.getProperty("17.3"))); + type17.getFields().put(4, toField(properties.getProperty("17.4"))); + type17.getFields().put(5, toField(properties.getProperty("17.5"))); + type17.getFields().put(6, toField(Integer.toString(image.getWidth()))); + type17.getFields().put(7, toField(Integer.toString(image.getHeight()))); + type17.getFields().put(8, toField(properties.getProperty("17.8"))); + type17.getFields().put(9, toField(Integer.toString(image.getWidth()))); + type17.getFields().put(10, toField(Integer.toString(image.getHeight()))); + type17.getFields().put(11, toField("PNG")); + type17.getFields().put(12, toField(Integer.toString(24))); + type17.getFields().put(13, toField("GRAY")); + type17.getFields().put(19, toField(properties.getProperty("17.19A"), + properties.getProperty("17.19B"), + properties.getProperty("17.19C"))); + + ByteArrayOutputStream target = new ByteArrayOutputStream(); + ImageIO.write(image, "PNG", target); + type17.setImageData(target.toByteArray()); + + ebts.addRecord(type1); + ebts.addRecord(type2); + ebts.addRecord(type17); + + return ebts; + } + + public void create() throws IOException, EbtsBuildingException { + Ebts ebts = new Ebts(); + Properties properties = new Properties(); + properties.load(ClassLoader.getSystemResourceAsStream("TransactionCreatorEbts.properties")); + + // Type-1 + LogicalRecord type1 = new GenericRecord(1); + type1.getFields().put(2, toField(properties.getProperty("1.2"))); + type1.getFields().put(4, toField(properties.getProperty("1.4"))); + type1.getFields().put(5, toField(properties.getProperty("1.5"))); + type1.getFields().put(7, toField(properties.getProperty("1.7"))); + type1.getFields().put(8, toField(properties.getProperty("1.8"))); + type1.getFields().put(9, toField(properties.getProperty("1.9"))); + type1.getFields().put(10, toField(properties.getProperty("1.10"))); + type1.getFields().put(11, toField(properties.getProperty("1.11"))); + type1.getFields().put(12, toField(properties.getProperty("1.12"))); + type1.getFields().put(13, toField(properties.getProperty("1.13A"), + properties.getProperty("1.13B"))); + type1.getFields().put(16, toField(properties.getProperty("1.16A"), + properties.getProperty("1.16B"), + properties.getProperty("1.16C"))); + + // Type-2 + LogicalRecord type2 = new GenericRecord(2); + type2.getFields().put(6, toField(properties.getProperty("2.6"))); + type2.getFields().put(10, toField(properties.getProperty("2.10A"), + properties.getProperty("2.10B"))); + type2.getFields().put(11, toField(properties.getProperty("2.11"))); + + // Type-17 + BufferedImage image = ImageIO.read(ClassLoader.getSystemResourceAsStream(properties.getProperty("17.999"))); + LogicalRecord type17 = new GenericRecord(17); + type17.getFields().put(3, toField(properties.getProperty("17.3"))); + type17.getFields().put(4, toField(properties.getProperty("17.4"))); + type17.getFields().put(5, toField(properties.getProperty("17.5"))); + type17.getFields().put(6, toField(Integer.toString(image.getWidth()))); + type17.getFields().put(7, toField(Integer.toString(image.getHeight()))); + type17.getFields().put(8, toField(properties.getProperty("17.8"))); + type17.getFields().put(9, toField(Integer.toString(image.getWidth()))); + type17.getFields().put(10, toField(Integer.toString(image.getHeight()))); + type17.getFields().put(11, toField("PNG")); + type17.getFields().put(12, toField(Integer.toString(24))); + type17.getFields().put(13, toField("GRAY")); + type17.getFields().put(19, toField(properties.getProperty("17.19A"), + properties.getProperty("17.19B"), + properties.getProperty("17.19C"))); + + ByteArrayOutputStream target = new ByteArrayOutputStream(); + ImageIO.write(image, "PNG", target); + type17.setImageData(target.toByteArray()); + + ebts.addRecord(type1); + ebts.addRecord(type2); + ebts.addRecord(type17); + + Files.write(new EbtsBuilder().build(ebts), new File("ebts.eft")); + } + + private static Field toField(String... subfields) { + Field f = new Field(); + Occurrence occurrence = new Occurrence(); + for(int i = 0; i < subfields.length; i++) { + occurrence.getSubFields().add(new SubField(subfields[i])); + } + f.getOccurrences().add(occurrence); + return f; + } +} diff --git a/support/src/main/java/org/mitre/iwp/support/SrbTransactionCreator.java b/support/src/main/java/org/mitre/iwp/support/SrbTransactionCreator.java new file mode 100644 index 0000000..74ccd6f --- /dev/null +++ b/support/src/main/java/org/mitre/iwp/support/SrbTransactionCreator.java @@ -0,0 +1,122 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.support; + +import com.google.common.io.Files; +import org.mitre.jet.ebts.Ebts; +import org.mitre.jet.ebts.EbtsBuilder; +import org.mitre.jet.ebts.field.Field; +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.mitre.jet.ebts.records.GenericRecord; +import org.mitre.jet.ebts.records.LogicalRecord; +import org.mitre.jet.exceptions.EbtsBuildingException; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.Properties; + +public class SrbTransactionCreator { + public static void main(String[] args) throws IOException, EbtsBuildingException { + new SrbTransactionCreator().create(); + } + + public void create() throws IOException, EbtsBuildingException { + Ebts ebts = new Ebts(); + Properties properties = new Properties(); + properties.load(ClassLoader.getSystemResourceAsStream("TransactionCreatorSrb.properties")); + + // Type-1 + LogicalRecord type1 = new GenericRecord(1); + type1.getFields().put(2, toField(properties.getProperty("1.2"))); + type1.getFields().put(4, toField(properties.getProperty("1.4"))); + type1.getFields().put(5, toField(properties.getProperty("1.5"))); + type1.getFields().put(7, toField(properties.getProperty("1.7"))); + type1.getFields().put(8, toField(properties.getProperty("1.8"))); + type1.getFields().put(9, toField(properties.getProperty("1.9"))); + type1.getFields().put(10, toField(properties.getProperty("1.10"))); + type1.getFields().put(11, toField(properties.getProperty("1.11"))); + type1.getFields().put(12, toField(properties.getProperty("1.12"))); + type1.getFields().put(13, toField(properties.getProperty("1.13A"), + properties.getProperty("1.13B"))); + type1.getFields().put(16, toField(properties.getProperty("1.16A"), + properties.getProperty("1.16B"), + properties.getProperty("1.16C"))); + + // Type-2 + LogicalRecord type2 = new GenericRecord(2); + type2.getFields().put(6, toField(properties.getProperty("2.6"))); + type2.getFields().put(79, toField(properties.getProperty("2.79"))); + type2.getFields().put(50, toField(properties.getProperty("2.2010"))); + + Field f2033 = new Field(); + for(int i = 1; i <= 2; i++) { + String[] occurrence = new String['N'-'A'+1]; + for(char c = 'A'; c <= 'N'; c++) { + occurrence[('N' - 'A') - ('N' - c)] = properties.getProperty(String.format("2.2033_%d%s", i, c)); + } + f2033.getOccurrences().add(toOccurrence(occurrence)); + } + type2.getFields().put(2033, f2033); + + ebts.addRecord(type1); + ebts.addRecord(type2); + + for(int i = 1; i <= 3; i++) { + // Type-17 + BufferedImage image = ImageIO.read(ClassLoader.getSystemResourceAsStream(properties.getProperty("17.999_"+i))); + LogicalRecord type17 = new GenericRecord(17); + type17.getFields().put(2, toField(properties.getProperty("17.2_"+i))); + type17.getFields().put(3, toField(properties.getProperty("17.3_"+i))); + type17.getFields().put(4, toField(properties.getProperty("17.4_"+i))); + type17.getFields().put(5, toField(properties.getProperty("17.5_"+i))); + type17.getFields().put(6, toField(Integer.toString(image.getWidth()))); + type17.getFields().put(7, toField(Integer.toString(image.getHeight()))); + type17.getFields().put(8, toField(properties.getProperty("17.8_"+i))); + type17.getFields().put(9, toField(Integer.toString(image.getWidth()))); + type17.getFields().put(10, toField(Integer.toString(image.getHeight()))); + type17.getFields().put(11, toField("PNG")); + type17.getFields().put(12, toField(Integer.toString(24))); + type17.getFields().put(13, toField("GRAY")); + type17.getFields().put(19, toField(properties.getProperty("17.19_"+i+"A"), + properties.getProperty("17.19_"+i+"B"), + properties.getProperty("17.19_"+i+"C"))); + + ByteArrayOutputStream target = new ByteArrayOutputStream(); + ImageIO.write(image, "PNG", target); + type17.setImageData(target.toByteArray()); + ebts.addRecord(type17); + } + + Files.write(new EbtsBuilder().build(ebts), new File("srb.eft")); + } + + private static Field toField(String... subfields) { + Field f = new Field(); + Occurrence occurrence = new Occurrence(); + for(int i = 0; i < subfields.length; i++) { + occurrence.getSubFields().add(new SubField(subfields[i])); + } + f.getOccurrences().add(occurrence); + return f; + } + + private static Occurrence toOccurrence(String... subfields) { + Occurrence occurrence = new Occurrence(); + for(int i = 0; i < subfields.length; i++) { + occurrence.getSubFields().add(new SubField(subfields[i])); + } + return occurrence; + } +} diff --git a/support/src/main/resources/Image.png b/support/src/main/resources/Image.png new file mode 100644 index 0000000..b85e925 Binary files /dev/null and b/support/src/main/resources/Image.png differ diff --git a/support/src/main/resources/TransactionCreatorEbts.properties b/support/src/main/resources/TransactionCreatorEbts.properties new file mode 100644 index 0000000..29a182e --- /dev/null +++ b/support/src/main/resources/TransactionCreatorEbts.properties @@ -0,0 +1,41 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +1.2=0500 +1.4=EBTS +1.5=20171201 +1.7=TARGETORI +1.8=SOURCEORI +1.9=TCN-123-456789 +1.10=TCR-123-456789 +1.11=00.00 +1.12=00.00 +1.13A=NORAM +1.13B=EBTS 10.0 +1.16A=APO +1.16B=APN +1.16C=1.0 + +2.6=SMITH,JOHN +2.10A=Investigation No. +2.10B=2017120101 +2.11=0000 + +17.3=1 +17.4=SOURCEORI +17.5=20171201 +17.8=1 +17.11=PNG +17.12=8 +17.13=GRAY +17.19A=EXAMPLE +17.19B=MODEL +17.19C=SN123 +17.999=Image.png \ No newline at end of file diff --git a/support/src/main/resources/TransactionCreatorSrb.properties b/support/src/main/resources/TransactionCreatorSrb.properties new file mode 100644 index 0000000..c7581db --- /dev/null +++ b/support/src/main/resources/TransactionCreatorSrb.properties @@ -0,0 +1,97 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +1.2=0500 +1.4=SRB +1.5=20171201 +1.7=SOURCEORI +1.8=TARGETORI +1.9=RESPONSE-1234 +1.10=TCN-123-456789 +1.11=00.00 +1.12=00.00 +1.13A=NORAM +1.13B=EBTS 10.0 +1.16A=APO +1.16B=APN +1.16C=1.0 + +2.6=SMITH,JOHN +2.79=2 +2.2010=50 +2.2033_1A=SUBJECT1 +2.2033_1B=SMITH,JOHN +2.2033_1C=4567 +2.2033_1D=11 +2.2033_1E= +2.2033_1F= +2.2033_1G=95 +2.2033_1H=40 +2.2033_1I= +2.2033_1J=2 +2.2033_1K= +2.2033_1L= +2.2033_1M= +2.2033_1N=12 +2.2033_2A=SUBJECT2 +2.2033_2B=JONES,JIM +2.2033_2C=9071 +2.2033_2D=11 +2.2033_2E= +2.2033_2F= +2.2033_2G=75 +2.2033_2H=40 +2.2033_2I= +2.2033_2J=3 +2.2033_2K= +2.2033_2L= +2.2033_2M= +2.2033_2N=12 + +# Probe +17.2_1=1 +17.3_1=1 +17.4_1=SOURCEORI +17.5_1=20171201 +17.8_1=1 +17.11_1=PNG +17.12_1=8 +17.13_1=GRAY +17.19_1A=EXAMPLE +17.19_1B=MODEL +17.19_1C=SN123 +17.999_1=Image.png + +# Candidate list +17.2_2=2 +17.3_2=1 +17.4_2=SOURCEORI +17.5_2=20170801 +17.8_2=1 +17.11_2=PNG +17.12_2=8 +17.13_2=GRAY +17.19_2A=MAKE2 +17.19_2B=MODEL2 +17.19_2C=SN456 +17.999_2=Image.png + +17.2_3=3 +17.3_3=1 +17.4_3=OTHERORI +17.5_3=20150801 +17.8_3=1 +17.11_3=PNG +17.12_3=8 +17.13_3=GRAY +17.19_3A=MAKE3 +17.19_3B=MODEL3 +17.19_3C=SN777 +17.999_3=Image.png \ No newline at end of file diff --git a/tshepii/CMakeLists.txt b/tshepii/CMakeLists.txt new file mode 100755 index 0000000..f276ea1 --- /dev/null +++ b/tshepii/CMakeLists.txt @@ -0,0 +1,122 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2026 The MITRE Corporation. All Rights Reserved. +# + +cmake_minimum_required(VERSION 2.8) +project(CryptDetector) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=gnu++0x -Wno-deprecated-declarations -Wno-sign-compare") + +find_package(OpenCV REQUIRED) +set(CD_SOURCE + src/Configuration.cpp + src/CryptDetector.cpp + src/CryptMatcher.cpp + src/Functions.cpp + src/Iris.cpp + src/IrisSegmenter.cpp + src/OsiCircle.cpp + src/OsiProcessings.cpp + src/Client.cpp + src/configparser.cpp +) + +find_package(Protobuf REQUIRED) +include_directories(${PROTOBUF_INCLUDE_DIRS}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +# Use the IAT's shared Protocol Buffer schemas rather than maintaining a +# component-local copy. +set(PROTOPATH "${CMAKE_CURRENT_SOURCE_DIR}/../buffers/src/main/proto") +set(PROTOFILES + "${PROTOPATH}/Tshepii.proto" + "${PROTOPATH}/Iwp.proto" +) + +# Fail early if the path or files are wrong +if(NOT EXISTS "${PROTOPATH}") + message(FATAL_ERROR "Shared proto directory not found: ${PROTOPATH}") +endif() + +foreach(_pf IN LISTS PROTOFILES) + if(NOT EXISTS "${_pf}") + message(FATAL_ERROR "Proto file not found: ${_pf}") + endif() +endforeach() + +# Pick protoc executable variable name (depends on CMake/FindProtobuf) +set(_PROTOC "${PROTOBUF_PROTOC_EXECUTABLE}") +if(NOT _PROTOC) + set(_PROTOC "${Protobuf_PROTOC_EXECUTABLE}") +endif() +if(NOT _PROTOC) + message(FATAL_ERROR "Could not determine protoc executable from FindProtobuf") +endif() + +# Generated outputs +set(GENERATED_PROTO_SRCS + "${CMAKE_CURRENT_BINARY_DIR}/Tshepii.pb.cc" + "${CMAKE_CURRENT_BINARY_DIR}/Iwp.pb.cc" +) +set(GENERATED_PROTO_HDRS + "${CMAKE_CURRENT_BINARY_DIR}/Tshepii.pb.h" + "${CMAKE_CURRENT_BINARY_DIR}/Iwp.pb.h" +) + +# Generate sources as part of the build (not only at configure time) +add_custom_command( + OUTPUT ${GENERATED_PROTO_SRCS} ${GENERATED_PROTO_HDRS} + COMMAND "${_PROTOC}" + --cpp_out "${CMAKE_CURRENT_BINARY_DIR}" + --proto_path "${PROTOPATH}" + ${PROTOFILES} + DEPENDS ${PROTOFILES} + COMMENT "Generating C++ sources from .proto files" + VERBATIM +) + +add_custom_target(generate_protos DEPENDS ${GENERATED_PROTO_SRCS} ${GENERATED_PROTO_HDRS}) + +if(PROTOBUF_VERBOSE) + message(STATUS "Using Protocol Buffers includes: ${PROTOBUF_INCLUDE_DIRS}") + message(STATUS "Using protoc: ${_PROTOC}") +endif() +# PROTOBUF_GENERATE_CPP(PROTO_SRCS PROTO_HDRS ${PROTOFILES}) + +if(PROTOBUF_VERBOSE) + message(STATUS "Using Protocol Buffers ${Protobuf_VERSION}") + message(STATUS "protobuf sources and header ${PROTO_SRCS} ${PROTO_HDRS}") +endif() + +find_package(CURL REQUIRED) +IF(CURL_FOUND) + INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR}) + SET(requiredlibs ${requiredlibs} ${CURL_LIBRARIES} ) +ELSE(CURL_FOUND) + MESSAGE(FATAL_ERROR "Could not find the CURL library and development files.") +ENDIF(CURL_FOUND) + +set(LOG4CXX_DIR /usr CACHE PATH "Path to Log4cxx") +file(GLOB LOG4CXX_INCLUDE ${LOG4CXX_DIR}/include/log4cxx) +include_directories(${LOG4CXX_INCLUDE}) +link_directories(${LOG4CXX_DIR}/lib) + +set(AMQCPP_DIR /usr CACHE PATH "Path to AmqCpp") +file(GLOB AMQCPP_INCLUDE ${AMQCPP_DIR}/include/activemq-cpp-*) +include_directories(${AMQCPP_INCLUDE}) +link_directories(${AMQCPP_DIR}/lib) + +add_executable(tshepii + ${CD_SOURCE} + ${GENERATED_PROTO_SRCS} +) +add_dependencies(tshepii generate_protos) +target_link_libraries(tshepii log4cxx activemq-cpp + ${OpenCV_LIBS} ${PROTOBUF_LIBRARIES} + ${CURL_LIBRARIES}) diff --git a/tshepii/Dockerfile b/tshepii/Dockerfile new file mode 100644 index 0000000..128dc66 --- /dev/null +++ b/tshepii/Dockerfile @@ -0,0 +1,74 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2026 The MITRE Corporation. All Rights Reserved. +# + +FROM debian:trixie-slim + +ARG ACTIVEMQ_CPP_VERSION=3.9.5 +ARG OPENCV_VERSION=2.4.13.7 +ARG DEBIAN_FRONTEND=non-interactive + +SHELL ["/bin/sh", "-euxc"] + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates wget curl \ + build-essential cmake pkg-config \ + autoconf automake libtool \ + protobuf-compiler libprotobuf-dev \ + libapr1-dev \ + libcurl4-openssl-dev \ + liblog4cxx-dev \ + zlib1g-dev \ + libjpeg62-turbo-dev libpng-dev libtiff-dev \ + && rm -rf /var/lib/apt/lists/* + +# Build ActiveMQ-CPP +RUN curl -fsSL "https://archive.apache.org/dist/activemq/activemq-cpp/${ACTIVEMQ_CPP_VERSION}/activemq-cpp-library-${ACTIVEMQ_CPP_VERSION}-src.tar.gz" \ + | tar xz \ + && cd "activemq-cpp-library-${ACTIVEMQ_CPP_VERSION}" \ + && ./configure \ + && make -j"$(nproc)" \ + && make install \ + && cd / \ + && rm -rf "activemq-cpp-library-${ACTIVEMQ_CPP_VERSION}" + +# Build OpenCV +RUN curl -fsSLk "https://github.com/opencv/opencv/archive/${OPENCV_VERSION}.tar.gz" \ + | tar xz \ + && mkdir -p "opencv-${OPENCV_VERSION}/build" \ + && cd "opencv-${OPENCV_VERSION}/build" \ + && cmake \ + -D CMAKE_CXX_FLAGS="-std=gnu++11" \ + -D WITH_OPENEXR=OFF \ + -D BUILD_opencv_videostab=OFF \ + -D BUILD_opencv_ts=OFF \ + .. \ + && make -j"$(nproc)" \ + && make install \ + && cd / \ + && rm -rf "opencv-${OPENCV_VERSION}" + +############################################################### + +COPY tshepii /tshepii +COPY buffers/src/main/proto /buffers/src/main/proto + +ENV TSHEPII_CONFIG_FILE=/etc/tshepii/tshepii.conf +ENV LOG4CXX_CONFIGURATION=/etc/tshepii/log4cxx.conf + +RUN mkdir -p /tshepii/build \ + && cd /tshepii/build \ + && cmake -DAMQCPP_DIR=/usr/local .. \ + && make -j"$(nproc)" \ + && install -m 0755 tshepii /usr/local/bin/tshepii \ + && mkdir -p /etc/tshepii \ + && cp -a /tshepii/config/* /etc/tshepii/ \ + && install -m 0755 /tshepii/start-tshepii.sh /usr/local/bin/start-tshepii + +CMD ["/usr/local/bin/start-tshepii"] diff --git a/tshepii/README.md b/tshepii/README.md new file mode 100644 index 0000000..5495cf4 --- /dev/null +++ b/tshepii/README.md @@ -0,0 +1,27 @@ +> +> NOTICE +> +> This software (or technical data) was produced for the U. S. Government and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV (May 2014) - Alternative IV (Dec 2007). +> +> (c) 2024 The MITRE Corporation. All Rights Reserved. +> + + +# TSHEPII Component + +The TSHEPII component is an Iris Analysis Toolkit (IAT) service that analyzes an iris image and returns detected crypts and an unrolled iris location. For more information about the original TSHEPII tool, see the [Original TSHEPII README](doc/README.md#tshepii). + +## Starting the Service +This service can be started with Docker through the IAT interface. Follow the [Quickstart for Users](../README.md#quickstart-for-users) instructions. + +If you make changes to the scripts and want to create a new Docker image, see [Rebuild Docker Image](#rebuild-docker-image). + +## Rebuild Docker Image +To build a new Docker image, run this command from the main `iat/` project directory: + ```sh + DOCKER_BUILDKIT=1 docker build --platform linux/amd64 \ + -t new_tshepii_image:YOUR_TAG_HERE \ + -f tshepii/Dockerfile . + ``` + +When running the image outside Docker Compose, provide non-empty `ARTEMIS_USER` and `ARTEMIS_PASSWORD` environment variables. The image does not provide default broker credentials. diff --git a/tshepii/config/log4cxx.conf b/tshepii/config/log4cxx.conf new file mode 100755 index 0000000..b6e3fe0 --- /dev/null +++ b/tshepii/config/log4cxx.conf @@ -0,0 +1,29 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +log4j.rootLogger=debug, stdout, R + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +# Pattern to output the caller's file name and line number. +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-40.40c{6}:%m%n + +log4j.appender.R=org.apache.log4j.RollingFileAppender +log4j.appender.R.File=logs/tshepii.log + +log4j.appender.R.MaxFileSize=100MB +# Keep one backup file +log4j.appender.R.MaxBackupIndex=1 + +log4j.appender.R.layout=org.apache.log4j.PatternLayout +log4j.appender.R.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-40.40c{6}:%m%n + +log4j.logger.org.MITRE = DEBUG diff --git a/iris-workstation/iwp-simple/iwp-tshepii/config/tshepii.conf b/tshepii/config/tshepii.conf similarity index 85% rename from iris-workstation/iwp-simple/iwp-tshepii/config/tshepii.conf rename to tshepii/config/tshepii.conf index e34fba7..24fca11 100755 --- a/iris-workstation/iwp-simple/iwp-tshepii/config/tshepii.conf +++ b/tshepii/config/tshepii.conf @@ -1,7 +1,7 @@ # # NOTICE # -# This software (or technical data) was produced for the U. S. Government +# This software (or technical data) was produced for the U. S. Government # and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV # (May 2014) – Alternative IV (Dec 2007) # @@ -9,6 +9,8 @@ # broker-URI=tcp://activemq:61616 +artemis-user=${ARTEMIS_USER} +artemis-password=${ARTEMIS_PASSWORD} tshepii-request-queue=iris.tshepii.request tshepii-response-queue=iris.tshepii.response allowed-img-exts=PNG,JP2,BMP,PIXMAP diff --git a/tshepii/doc/README.md b/tshepii/doc/README.md new file mode 100755 index 0000000..956ad51 --- /dev/null +++ b/tshepii/doc/README.md @@ -0,0 +1,158 @@ +# TSHEPII # + +A Software Tool Supporting the Human Examination of Post-mortem Iris Images. + +## Features ## + +- Pairwise comparison of iris images, particularly tuned for post-mortem samples. +- Automatic iris segmentation provided by integration with the OSIRIS [1] software. +- Cross-platform rich GUI for: + - Manual iris segmentation. + - Free-form annotation of mathing and non-matching iris regions. + - Basic iris image processing, such as zooming, sharpness, brightness, and contrast enhancements. +- Iris-comparison report generation. +- Examination saving and loading for work pause and resume. +- Eleven methods of pairwise iris comparison: + 1. OSIRIS [1] Gabor-filter-based comparison score. + 2. BSIF-inspired [2] comparison score. + 3. Crypt-based [3] comparison score. + 4. TSHEPII[1](#f1) matched-keypoint comparison score. + 5. TSHEPII[1](#f1) unmatched-keypoint comparison score. + 6. SURF [4] matched-keypoint comparison score. + 7. SURF [4] unmatched-keypoint comparison score. + 8. SIFT [5] matched-keypoint comparison score. + 9. SIFT [5] unmatched-keypoint comparison score. + 10. MSER [6] matched-region comparison score. + 11. MSER [6] unmatched-region comparison score. +- Global comparison score based on majority voting of the available eleven methods. + +## Screenshots ## + +![Windows 10 main screen](screens/main_win10.png?raw=true) +![macOS High Sierra main screen](screens/main_cocoa.png?raw=true) + +## Source Code ## + +TSHEPII source code is available on CVRL[2](#f2) [GitHub](https://github.com/CVRL/TSHEPII) (private repository). +It currently has the following branches: + +Branch | Description +------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------- +[2018-apr-deliverable](https://github.com/CVRL/TSHEPII/tree/2018-apr-deliverable) | 2018 April deliverable. Complete version of the software, with all the features. +[2018-feb-deliverable](https://github.com/CVRL/TSHEPII/tree/2018-feb-deliverable) | 2018 February deliverable. Deprecated. Software prototype, containing only the main features. +[keypoint-adding-tutorial](https://github.com/CVRL/TSHEPII/tree/keypoint-adding-tutorial) | Extended version of the software, as a result of the software extension tutorial. +[master](https://github.com/CVRL/TSHEPII/tree/master) | Main development code. It can be updated without notifications. + +## Compilation ## + +### Required Software ### + +- [OpenCV 2.4.13](https://github.com/opencv/opencv/releases/tag/2.4.13). +- MS Visual Studio 2015 (or superior) +- PROTOBUF_VERSION="3.5.1" +**OR** +[CMake](https://cmake.org/download/) 2.8 (or superior). +- In case of macOS High Sierra, [AppKit](https://developer.apple.com/documentation/appkit). +- In case of Ubuntu 16.04, [GTK+ 3](https://developer.gnome.org/gtk3/3.0/). + +### MS Visual Studio ### + +TSHEPII was developed with MS Visual Studio 2015 (MVS): + +1. Check-out GitHub [master](https://github.com/CVRL/TSHEPII/tree/master) branch. + + ``` git + git clone https://github.com/CVRL/TSHEPII/tree/master + ``` + +2. Use [TSHEPII.sln](TSHEPII2.sln) file for correctly opening the MVS project. + +![MVS TSHEPII Project](screens/mvs.png?raw=true) + +### CMake-based Compilation ### + +Alternatively, CMake can be used for compiling TSHEPII in a cross-platform way: + +1. Check-out GitHub [master](https://github.com/CVRL/TSHEPII/tree/master) branch. + + ``` git + git clone https://github.com/CVRL/TSHEPII/tree/master + ``` + +2. Set the correct *file separator* character in [Configuration.cpp](TSHEPII2/Configuration.cpp). + Search for the *FILE_SEPARATOR* variable and follow the commented instructions. + + ``` C + /* General configuration. */ + const string Configuration::FILE_SEPARATOR = "\\"; // Unix-like systems: change to "/" + ``` + +3. Create *build* folder. + + ``` bash + mkdir build; cd build + ``` + +4. Run *cmake* and *make* commands + + ``` bash + cmake .. + make + ``` + +5. Execute the application + + ``` bash + ./TSHEPII2 + ``` + +## Documentation ## + +TSHEPII documentation is automatically generated with [Doxygen](http://www.stack.nl/~dimitri/doxygen/). +It is available [here](docs/html/index.html). + +Additionally, there is a tutorial for adding other keypoint-based iris-comparison methods. +It is available [here](tutorial.pdf). + +## Third-party Libraries ## + +- [OpenCV 2.4.13](https://github.com/opencv/opencv/releases/tag/2.4.13). +- [CVUI](https://github.com/Dovyski/cvui). +- [Native File Dialog](https://github.com/mlabbe/nativefiledialog). +- OSIRIS [1]. + +## About ## + +Developed by: + +- University of Notre Dame. +- West Virginia University. + +Developed for: + +- The FBI Biometric Center of Excellence. + +Development Team: + +- Dr. Adam Czajka, Lead-PI. +- Dr. Kevin Bowyer, Co-PI. +- Dr. Patrick Flynn, Co-PI. +- Dr. Daniel Moreira, Post-doc Researcher. +- Ms. Natasha Scritchfield, Project Manager. + +Support Contact: + +- Dr. [Adam Czajka](aczajka@nd.edu). +- Dr. [Daniel Moreira](dhenriq1@nd.edu). + +## References ## + +- [1] N. Othman, B. Dorizzi, and S. Garcia-Salicetti. OSIRIS: An open source iris recognition software. Elsevier Pattern Recognition Letters, 82(2):124–131, 2016. +- [2] J. Kannala and E. Rahtu. BSIF: Binarized statistical image features. In Proceedings of the Intl. Conference on Pattern Recognition (ICPR), pp. 1363–1366, 2012. +- [3] J. Chen, F. Shen, D. Chen, and P. Flynn. Iris Recognition Based on Human-Interpretable Features. IEEE Transactions on Information Forensics and Security, 11(7):1476–1485, 2016. +- [4] H. Bay, A. Ess, T. Tuytelaars, and L. Van Gool. Speeded-up robust features (SURF). Computer Vision and Image Understanding, 110(3):346–359, 2008. +- [5] D. Lowe. Distinctive image features from scale-invariant keypoints. Springer Intl. Journal of Computer Vision, 60(2):91–110, 2004. +- [6] J. Matas, O. Chum, M. Urban, and T. Pajdla. Robust Wide Baseline Stereo from Maximally Stable Extremal Regions. Image and Vision Computing, 22(10):761–767, 2004. + +1 Novel iris-texture detector and descriptor developed in the context of the TSHEPII project. [↩](#a1) +2 CVRL: The Computer Vision Research Lab at the University of Notre Dame. [↩](#a2) diff --git a/tshepii/src/Client.cpp b/tshepii/src/Client.cpp new file mode 100755 index 0000000..52f1510 --- /dev/null +++ b/tshepii/src/Client.cpp @@ -0,0 +1,647 @@ +// ####################################################################### +// NOTICE +// +// This software (or technical data) was produced for the U. S. Government +// and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +// (May 2014) – Alternative IV (Dec 2007) +// +// (c) 2024 The MITRE Corporation. All Rights Reserved. +// ####################################################################### + +#include "Client.h" +#include "CryptDetector.hpp" +#include "Iris.hpp" +#include "Configuration.hpp" +#include "configparser.h" +#include "CryptMatcher.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using activemq::core::ActiveMQConnectionFactory; +using log4cxx::Logger; +using org::mitre::iwp::buffers::Crypt; +using org::mitre::iwp::buffers::ImageServiceQuery; +using org::mitre::iwp::buffers::TCircle; +using org::mitre::iwp::buffers::TPoint; +using org::mitre::iwp::buffers::TShepiiCompareResponse; +using org::mitre::iwp::buffers::TShepiiResponse; +using std::exception; +using std::find; +using std::string; +using std::transform; +using std::unique_ptr; +using std::vector; +using std::chrono::seconds; +using std::this_thread::sleep_for; + +const extern log4cxx::LoggerPtr logger = + Logger::getLogger("org.MITRE.cryptDectector.client"); + +#define CURL_STATICLIB + +void Client::cleanup() +{ + try + { + if (connection != NULL) + { + LOG4CXX_INFO(logger, "Connection found, closing the existing conneciton!"); + connection->close(); + } + } + catch (CMSException &e) + { + LOG4CXX_ERROR(logger, "Error while closing connection: " << e.what()); + e.printStackTrace(); + } + + delete request_dest; + request_dest = NULL; + delete response_dest; + response_dest = NULL; + delete consumer; + consumer = NULL; + delete producer; + producer = NULL; + delete session; + session = NULL; + delete connection; + connection = NULL; + LOG4CXX_INFO(logger, "Connection resources destroyed"); +} + +Client::~Client() { this->cleanup(); } + +Client::Client(const string &broker_uri, + const string &artemis_user, + const string &artemis_password, + const string &requests_dest_name, + const string &response_dest_name, + const string &output_path, + const vector allowed_img_extensions) +{ + + this->connection = NULL; + this->session = NULL; + this->request_dest = NULL; + this->response_dest = NULL; + this->consumer = NULL; + this->producer = NULL; + this->broker_uri = broker_uri; + this->artemis_user = artemis_user; + this->artemis_password = artemis_password; + this->requests_dest_name = requests_dest_name; + this->response_dest_name = response_dest_name; + this->output_path = output_path; + this->allowed_img_extensions = allowed_img_extensions; +} + +// Use this for iris-workstation integration +Client::Client(const string &broker_uri, + const string &artemis_user, + const string &artemis_password, + const string &requests_dest_name, + const string &response_dest_name, + const vector allowed_img_extensions) +{ + + GOOGLE_PROTOBUF_VERIFY_VERSION; + + this->broker_uri = broker_uri; + this->artemis_user = artemis_user; + this->artemis_password = artemis_password; + this->requests_dest_name = requests_dest_name; + this->allowed_img_extensions = allowed_img_extensions; + + this->connection = NULL; + this->session = NULL; + this->request_dest = NULL; + this->response_dest = NULL; + this->consumer = NULL; + this->producer = NULL; +} + +void Client::runRequestConsumer() +{ + int attempts = 0; + const int max_attempts = 5; + bool connected = false; + ActiveMQConnectionFactory *connection_factory = nullptr; + + while (attempts < max_attempts && !connected) { + try + { + LOG4CXX_INFO(logger, "Initializing client attempt #" << attempts + 1 << " out of: " << max_attempts); + + // obtain new ConnectionFactory + connection_factory = new ActiveMQConnectionFactory(broker_uri); + connection = connection_factory->createConnection(artemis_user, artemis_password); + + connected = true; + LOG4CXX_INFO(logger, "Successfully connected to ActiveMQ broker"); + } + catch (CMSException e) + { + attempts++; + LOG4CXX_WARN(logger, "Connection attempt #" << attempts << " failed. Error: " << e.what()); + + // Clean up the factory if it was allocated but connection failed + if (connection_factory) { + delete connection_factory; + connection_factory = nullptr; + } + + if (attempts < max_attempts) { + LOG4CXX_INFO(logger, "Retrying in 5 seconds..."); + std::this_thread::sleep_for(std::chrono::seconds(5)); + } else { + LOG4CXX_ERROR(logger, "Reached maximum connection attempts. Exiting."); + LOG4CXX_ERROR(logger, "Make sure the ActiveMQ server is accessible at " << broker_uri); + exit(1); + } + } + } + + try + { + // start connection now that consumers created + connection->start(); + connection->setExceptionListener(this); + + session = connection->createSession(Session::DUPS_OK_ACKNOWLEDGE); + request_dest = session->createQueue(requests_dest_name); + response_dest = session->createQueue(response_dest_name); + // Setup a producer to put messages on IWP response queue + producer = session->createProducer(response_dest); + producer->setDeliveryMode(DeliveryMode::NON_PERSISTENT); + + LOG4CXX_DEBUG(logger, "Using request queue: " << requests_dest_name); + LOG4CXX_DEBUG(logger, "Using response queue: " << response_dest_name); + + // Setup a consumer to consume messages off IWP requst queue + consumer = session->createConsumer(request_dest); + // Asynchronous MessageListener consumption + consumer->setMessageListener(this); + + delete connection_factory; // connection created, delete connection_factory + LOG4CXX_INFO(logger, "Finished client initialization"); + } + catch (CMSException e) + { + LOG4CXX_ERROR(logger, "Error occured during client initialization: " << e.what() << ". Exiting."); + e.printStackTrace(); + exit(1); + } +} + +void Client::handle_crypt_query(TShepiiResponse *crypt_response, ImageServiceQuery *query) +{ + LOG4CXX_INFO(logger, "Processing message..."); + if (!query->path().empty()) + { + LOG4CXX_DEBUG(logger, "Received image URL " << query->path()); + } + LOG4CXX_INFO(logger, "Message ID " << query->imageid()); + + crypt_response->set_imageid(query->imageid()); + + string image_path = Client::getImage(query->imageid(), query->path(), + query->imagebytes()); + int image_type = query->imagetype(); + int image_width = query->width(); + int image_height = query->height(); + + LOG4CXX_INFO(logger, "Analyzing " << image_path); + Iris *iris; + if (image_type == ImageServiceQuery::PIXMAP) + { + iris = new Iris(image_path, image_width, image_height); + } + else + { + iris = new Iris(image_path); + } + + iris->osirisPreProcess(); + Mat inputIris = iris->getGray3CCrop().clone(); + Mat inputIrisMask = iris->getCropMask().clone(); + Mat outputIris = iris->getGray3CCrop().clone(); + Mat originalIris = iris->getColorCrop().clone(); + + CryptDetector *detector = new CryptDetector( + &inputIris, &inputIrisMask, &outputIris, &originalIris, + iris->getCropRadius(), iris->getCropCenter().x, iris->getCropCenter().y, + iris->getCropPupilRadius(), iris->getCropCenter().x, iris->getCropCenter().y, + Configuration::CRYPTS_COLOR); + + detector->detectCrypts(); + + if (!image_path.empty()) + { + remove(image_path.c_str()); // remove temp file + } + + this->initCircle( + crypt_response->mutable_cropiris(), + iris->getCropCenter().x, + iris->getCropCenter().y, + iris->getCropRadius()); + + this->initCircle( + crypt_response->mutable_croppupil(), + iris->getCropPupilCenter().x, + iris->getCropPupilCenter().y, + iris->getCropPupilRadius()); + + this->initCircle( + crypt_response->mutable_originaliris(), + iris->getOriginalIrisCenter().x, + iris->getOriginalIrisCenter().y, + iris->getOriginalIrisRadius()); + + this->initCircle( + crypt_response->mutable_originalpupil(), + iris->getOriginalPupilCenter().x, + iris->getOriginalPupilCenter().y, + iris->getOriginalPupilRadius()); + + for (auto const &it : *(detector->getSortedKeypoints())) + { + TPoint *k = crypt_response->add_keypoints(); + k->set_x(it.pt.x); + k->set_y(it.pt.y); + k->set_size(it.size); + } + for (auto const &crypt : detector->crypts) + { + Crypt *c = crypt_response->add_crypts(); + for (auto const &point : crypt) + { + TPoint *p = c->add_points(); + p->set_x(point.x); + p->set_y(point.y); + } + } + crypt_response->set_inputimagewidth(inputIris.cols); + crypt_response->set_inputimageheight(inputIris.rows); +} + +void Client::handle_compare_query(TShepiiCompareResponse *reply, TShepiiCompareRequest *query) +{ + TShepiiResponse *response = query->mutable_response1(); + TShepiiResponse *response2 = query->mutable_response2(); + CryptMatcher *matcher = new CryptMatcher(response, response2); + matcher->detectDescribeAndMatch(); + reply->mutable_cryptresponse()->CopyFrom(*response); + reply->mutable_cryptresponse2()->CopyFrom(*response2); +} + +void Client::onMessage(const Message *message) +{ + try + { + LOG4CXX_INFO(logger, "Message received"); + vector message_properties = message->getPropertyNames(); + + const BytesMessage *msg = dynamic_cast(message); + if (!msg) + { + LOG4CXX_ERROR(logger, "Incorrect message type received from " + "ActiveMQ, aborting"); + return; + } + LOG4CXX_INFO(logger, "Message body length: " << msg->getBodyLength()); + + unsigned char *request_bytes; + int request_length; + ImageServiceQuery crypt_query; + TShepiiCompareRequest compare_query; + + TShepiiResponse crypt_reply; + TShepiiCompareResponse compare_reply; + + LOG4CXX_INFO(logger, "Message ID " << crypt_query.imageid()); + + request_bytes = msg->getBodyBytes(); + request_length = msg->getBodyLength(); + + bool isCryptReply = true; + + if (compare_query.ParseFromArray(request_bytes, request_length)) + { + LOG4CXX_INFO(logger, "Received compare request"); + isCryptReply = false; + this->handle_compare_query(&compare_reply, &compare_query); + } + else if (crypt_query.ParseFromArray(request_bytes, request_length)) + { + isCryptReply = true; + LOG4CXX_INFO(logger, "Received crypt request"); + this->handle_crypt_query(&crypt_reply, &crypt_query); + } + else + { + LOG4CXX_ERROR(logger, "Error occured parsing the request"); + return; + } + + // Copy header info to response + // ByteSize() const' is deprecated: Please use ByteSizeLong() + size_t crypt_reply_size = crypt_reply.ByteSizeLong(); + size_t compare_reply_size = compare_reply.ByteSizeLong(); + + size_t reply_size = crypt_reply_size; + + if (isCryptReply) + { + reply_size = crypt_reply_size; + } + else + { + reply_size = compare_reply_size; + } + + unsigned char reply_bytes[reply_size]; + + if (isCryptReply) + { + crypt_reply.SerializeToArray(reply_bytes, reply_size); + LOG4CXX_INFO(logger, "Sending Crypt Response"); + } + else + { + compare_reply.SerializeToArray(reply_bytes, reply_size); + LOG4CXX_INFO(logger, "Sending Compare Response"); + } + + LOG4CXX_INFO(logger, "Reply size: " << reply_size); + + unique_ptr response( + session->createBytesMessage(reply_bytes, reply_size)); + LOG4CXX_INFO(logger, "Response sent: " << reply_bytes); + producer->send(response.get()); + + LOG4CXX_INFO(logger, "Response sent for ID: " << crypt_query.imageid()); + } + catch (CMSException &e) + { + LOG4CXX_ERROR(logger, "Error occured during processing: " << e.what()); + e.printStackTrace(); + } + catch (exception &e) + { + LOG4CXX_ERROR(logger, "Error occured during processing: " << e.what()); + } + catch (...) + { + LOG4CXX_ERROR(logger, "An unknown error occured during processing"); + } +} + +void Client::initCircle(TCircle *circle, double x, double y, double radius) +{ + TPoint *center = circle->mutable_center(); + center->set_x(x); + center->set_y(y); + circle->set_radius(radius); +} + +void Client::onException(const CMSException &ex) +{ + LOG4CXX_ERROR(logger, "Exception occured: " << ex.what() << ". Exiting."); + exit(1); +} + +void Client::transportInterrupted() +{ + LOG4CXX_WARN(logger, "Transport interupted"); +} + +void Client::transportResumed() +{ + LOG4CXX_WARN(logger, "Transport resumed"); +} + +bool Client::imgAllowed(string &img_ext) +{ + bool ret = find(allowed_img_extensions.begin(), + allowed_img_extensions.end(), img_ext) != allowed_img_extensions.end(); + if (!ret) + { + LOG4CXX_ERROR(logger, "Image type '" << img_ext << "' not allowed"); + } + return ret; +} + +string Client::getImage(string id, string url_path, + const string bytes) +{ + CURL *curl; + FILE *fp; + CURLcode res; + + string str_path = this->output_path + id; + + if (!url_path.empty()) + { + LOG4CXX_INFO(logger, "URL - " << url_path); + } + LOG4CXX_INFO(logger, "Output file name " << str_path); + + string local_path = ""; + + try + { + curl = curl_easy_init(); + if (curl) + { + fp = fopen(str_path.c_str(), "wb"); + if (fp) + { + if (url_path.length()) + { + curl_easy_setopt(curl, CURLOPT_URL, url_path.c_str()); + curl_easy_setopt(curl, CURLOPT_HEADER, false); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Client::write_data); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + res = curl_easy_perform(curl); + if (res != CURLE_OK) + { + LOG4CXX_ERROR(logger, "curl_easy_perform() failed: %s\n" + << curl_easy_strerror(res) << "\n"); + } + curl_easy_cleanup(curl); + } + else + { + fwrite(bytes.c_str(), sizeof(char), bytes.length(), fp); + } + fclose(fp); + } + else + { + LOG4CXX_ERROR(logger, "Unable to open output file " << str_path); + return ""; + } + local_path = str_path; + } + else + { + LOG4CXX_ERROR(logger, "Error initialize curl object"); + } + } + catch (exception &e) + { + LOG4CXX_ERROR(logger, "Error downloading image to local " << e.what()); + } + + return local_path; +} + +size_t Client::write_data(void *contents, size_t size, size_t nmemb, + FILE *stream) +{ + size_t written; + written = fwrite(contents, size, nmemb, stream); + + return written; +} + +void Client::loadImage(const string &rFilename, Mat **image) +{ + try + { + if (!*image) + { + LOG4CXX_ERROR(logger, "Cannot load image : " << rFilename); + } + } + catch (exception &e) + { + LOG4CXX_ERROR(logger, e.what()); + } +} + +void Client::loadPixmap(const string &rFilename, Mat **image, int imageHeight, + int imageWidth) +{ + + int channels; + Mat mat; + FILE *fp = fopen(rFilename.c_str(), "rb"); + if (!fp) + { + LOG4CXX_ERROR(logger, "Cannot load image : " << rFilename); + return; + } + // Determine the number of channels + fseek(fp, 0, SEEK_END); + channels = ftell(fp) / imageHeight / imageWidth; + fseek(fp, 0, SEEK_SET); + if (channels == 3) + { + mat = Mat::zeros(imageHeight, imageWidth, CV_8UC3); + fread(mat.data, 3, imageWidth * imageHeight, fp); + } + else + { + mat = Mat::zeros(imageHeight, imageWidth, CV_8UC1); + fread(mat.data, 1, imageWidth * imageHeight, fp); + } + *image = &mat; + fclose(fp); +} + +/** + * Determines whether a file or directory exists and is readable. + * + * @param filename The path to the desired file or directory. + * @return true if it exists and is readable, false otherwise. + */ +bool Client::fileExists(const string &filename) +{ + struct stat info; + if (stat(filename.c_str(), &info)) + return false; +#ifdef _WIN32 + return (bool)(info.st_mode); +#else + return (bool)(info.st_mode & (S_IXOTH | S_IROTH)); +#endif +} + +int main(int argc, char **argv) +{ + if (argc < 3) + { + std::cout << "Usage " << argv[0] << " " + << std::endl; + return 0; + } + + if (!Client::fileExists(argv[1])) + { + LOG4CXX_ERROR(logger, "Error loading tshepii component config. Missing " + "file. Exiting."); + return -1; + } + if (!Client::fileExists(argv[2])) + { + LOG4CXX_ERROR(logger, "Error loading logger config. Missing file. " + "Exiting."); + return -1; + } + + ConfigParser cp; + string config_file(argv[1]); + map params; + bool success = cp.parseParams(config_file, params); + if (!success) + { + LOG4CXX_ERROR(logger, "Error loading tshepii component config. Exiting."); + return 1; + } + + // Assume alowed img extensions are contained in comma-delimited string + char delim = ','; + vector allowed_img_extensions; + cp.parseDelimitedString( + allowed_img_extensions, params["allowed-img-exts"], delim); + + Client c = Client(params["broker-URI"], + params["artemis-user"], + params["artemis-password"], + params["tshepii-request-queue"], + params["tshepii-response-queue"], + params["tshepii-output-path"], + allowed_img_extensions); + + if (Client::fileExists(argv[2])) + { + log4cxx::PropertyConfigurator::configure(argv[2]); + } + log4cxx::LoggerPtr logger = log4cxx::Logger::getRootLogger(); + log4cxx::helpers::Pool p; + + activemq::library::ActiveMQCPP::initializeLibrary(); + vector extensions = {"PNG"}; + c.runRequestConsumer(); + + while (true) + { + sleep_for(seconds(1)); + } + + activemq::library::ActiveMQCPP::shutdownLibrary(); + return 0; +} diff --git a/tshepii/src/Client.h b/tshepii/src/Client.h new file mode 100755 index 0000000..c39732e --- /dev/null +++ b/tshepii/src/Client.h @@ -0,0 +1,119 @@ +// ####################################################################### +// NOTICE +// +// This software (or technical data) was produced for the U. S. Government +// and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +// (May 2014) – Alternative IV (Dec 2007) +// +// (c) 2024 The MITRE Corporation. All Rights Reserved. +// ####################################################################### + +#ifndef TSHEPII_CLIENT_INCLUDE_CLIENT_H_ +#define TSHEPII_CLIENT_INCLUDE_CLIENT_H_ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "Iwp.pb.h" +#include "Tshepii.pb.h" + +using activemq::transport::DefaultTransportListener; +using cms::BytesMessage; +using cms::CMSException; +using cms::Connection; +using cms::DeliveryMode; +using cms::Destination; +using cms::ExceptionListener; +using cms::Message; +using cms::MessageConsumer; +using cms::MessageListener; +using cms::MessageProducer; +using cms::Session; +using cv::Mat; +using org::mitre::iwp::buffers::TCircle; +using std::string; +using std::vector; +using org::mitre::iwp::buffers::ImageServiceQuery; +using org::mitre::iwp::buffers::TShepiiCompareRequest; +using org::mitre::iwp::buffers::TShepiiCompareResponse; +using org::mitre::iwp::buffers::TShepiiResponse; + +class Client : public MessageListener, public ExceptionListener, public DefaultTransportListener +{ + +private: + Session *session; + Connection *connection; + Destination *request_dest; + Destination *response_dest; + MessageConsumer *consumer; + MessageProducer *producer; + string broker_uri; + string artemis_user; + string artemis_password; + string requests_dest_name; + string response_dest_name; + string output_path; + vector allowed_img_extensions; + + Client &operator=(const Client &); + void cleanup(); + string getImage(string id, string url, + const string bytes); + +public: + ~Client(); + Client(const string &broker_uri, + const string &artemis_user, + const string &artemis_password, + const string &requests_dest_name, + const string &response_dest_name, + const string &output_path, + const vector allowed_img_extensions); + + Client(const string &broker_uri, + const string &artemis_user, + const string &artemis_password, + const string &requests_dest_name, + const string &response_dest_name, + const vector allowed_img_extensions); + + void runRequestConsumer(); + void handle_crypt_query(TShepiiResponse *response, ImageServiceQuery *query); + void handle_compare_query(TShepiiCompareResponse *response, TShepiiCompareRequest *query); + void onMessage(const Message *message); + void onException(const CMSException &ex); + void transportInterrupted(); + void transportResumed(); + bool imgAllowed(string &imgExt); + void initCircle(TCircle *circle, double x, double y, double radius); + static size_t write_data(void *contents, size_t size, size_t nmemb, + FILE *stream); + static bool fileExists(const string &filename); + + void loadImage(const string &rFilename, Mat **image); + void loadPixmap(const string &rFilename, Mat **image, int, int); +}; + +#endif // ACII_CLIENT_INCLUDE_CLIENT_H_ diff --git a/tshepii/src/Configuration.cpp b/tshepii/src/Configuration.cpp new file mode 100755 index 0000000..5c20490 --- /dev/null +++ b/tshepii/src/Configuration.cpp @@ -0,0 +1,555 @@ +// ####################################################################### +// NOTICE +// +// This software (or technical data) was produced for the U. S. Government +// and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +// (May 2014) – Alternative IV (Dec 2007) +// +// (c) 2024 The MITRE Corporation. All Rights Reserved. +// ####################################################################### + +/** @file Implementation of Configuration class. */ + +#include "Configuration.hpp" + +/* General configuration. */ +const string Configuration::FILE_SEPARATOR = "\\"; // Unix-like systems: change to "/" +const int Configuration::PROCESSED_IRIS_IMAGE_COUNT = 15; +const int Configuration::SCORE_COUNT = 11; + +/* OSIRIS configuration. */ +const double Configuration::OSIRIS_THRESHOLD = 0.4461; +int Configuration::OSIRIS_MIN_IRIS_DIAMETER = 160; +int Configuration::OSIRIS_MIN_PUPIL_DIAMETER = 48; +int Configuration::OSIRIS_MAX_IRIS_DIAMETER = 380; +int Configuration::OSIRIS_MAX_PUPIL_DIAMETER = 160; +int Configuration::OSIRIS_NORM_IMAGE_COLS = 512; +int Configuration::OSIRIS_NORM_IMAGE_ROWS = 64; + +/* BSIF configuration. */ +const double Configuration::BSIF_THRESHOLD = 0.4216; +const int Configuration::BSIF_NORM_IMAGE_COLS = 256; +const int Configuration::BSIF_NORM_IMAGE_ROWS = 32; + +/* Iris content pre-processing. */ +const int Configuration::IRIS_MASK_BLUR_SIZE = 5; +const int Configuration::IRIS_CLAHE_TILES = 16; +const double Configuration::IRIS_CLAHE_CLIP = 2.0; + +/* Interest point configuration. */ +// All configurations +const int Configuration::KP_LINE_WIDTH = 1; +const int Configuration::KP_MIN_SIZE = 9; +const int Configuration::KP_MAX_SIZE = 76; +const double Configuration::KP_COINCIDENCE_TOLERANCE = 0.5; +const int Configuration::KP_MATCHER_KNN_K = 50; +const double Configuration::KP_MATCHER_NORM_L2_QUALITY_RATIO = 0.8; +const double Configuration::KP_MATCHER_NORM_HAMMING_QUALITY_RATIO = 0.5; +const double Configuration::KP_MATCHER_DEVIATION_TOLERANCE = 1.5; +const double Configuration::KP_MATCHER_POLAR_RADIUS_TOLERANCE = 0.075; +const double Configuration::KP_MATCHER_POLAR_ANGLE_TOLERANCE = 0.2; + +// interest point detectors +// Crypts +const double Configuration::CRYPT_THRESHOLD = 0.6389; +const int Configuration::CRYPTS_LINE_WIDTH = 1; +const int Configuration::CRYPTS_SMOOTH_GAUSS_SIZE = 5; // original value +const int Configuration::CRYPTS_SMOOTH_GAUSS_SIGMA = 3; // original value +const int Configuration::CRYPTS_MIN_ITERATIVE_STR_EL_SIZE = 7; // original value +const int Configuration::CRYPTS_INITIAL_ITERATIVE_STR_EL_SIZE = 17; // original value +const double Configuration::CRYPTS_MIN_CRYPT_AREA = 49.0; // 7 x 7 +const double Configuration::CRYPTS_MAX_CRYP_AREA_LB = 652.0; // 384 * 384 * <145> / (512 * 64) +const double Configuration::CRYPTS_MIN_CRYP_AREA_UB = 1426.0; // 384 * 384 * <317> / (512 * 64) +const double Configuration::CRYPTS_CUT_STD = 0.06; // original value +const double Configuration::CRYPTS_MATCH_COINCIDENCE_TOLERANCE = 0.5; // 50% of crypt coincidence +const double Configuration::CRYPTS_MATCH_DIST_ALPHA = 0.5; // coincidence and area complementary weights +const Scalar Configuration::CRYPTS_COLOR(255, 255, 0); + +// TSHEPII +const double Configuration::TSHEPII_MATCH_THRESHOLD = 0.0105; +const double Configuration::TSHEPII_MISMATCH_THRESHOLD = 0.0022; +const double Configuration::TSHEPII_BORDER_AVOIDANCE = 1.5; +const double Configuration::TSHEPII_OVERLAP_TOLERANCE = 1.0; +const double Configuration::TSHEPII_KP_THRESHOLD = 1.0; +const int Configuration::TSHEPII_CELL_DIVISION = 4; +const double Configuration::TSHEPII_BIN_THRESHOLD = 1.0; +const string Configuration::TSHEPII_FILTER_LIST_FILEPATH = "tshepii_filter_list.txt"; +const string Configuration::TSHEPII_ZND_LIST_FILEPATH = "tshepii_zn_list.txt"; +const Scalar Configuration::TSHEPII_COLOR(255, 0, 0); + +// SURF +const double Configuration::SURF_MATCH_THRESHOLD = 0.2358; +const double Configuration::SURF_MISMATCH_THRESHOLD = 0.3361; +const double Configuration::SURF_HESSIAN_THRESH = 1.0; +const Scalar Configuration::SURF_COLOR(0, 130, 255); + +// SIFT +const double Configuration::SIFT_MATCH_THRESHOLD = 0.3932; +const double Configuration::SIFT_MISMATCH_THRESHOLD = 0.5156; +const int Configuration::SIFT_KP_COUNT = 0; // > zero means all possible interest points +const int Configuration::SIFT_OCTAVE_LAYER_COUNT = 3; // > 3 is the lieterature suggested value +const double Configuration::SIFT_CONTRAST_THRESH = 0.0; // > the lesser this value, the more detected interest points +const double Configuration::SIFT_EDGE_THRESH = 1000.0; // > the more this value, the more detected interest points +const Scalar Configuration::SIFT_COLOR(255, 0, 255); + +// MSER +const double Configuration::MSER_MATCH_THRESHOLD = 0.2796; +const double Configuration::MSER_MISMATCH_THRESHOLD = 0.1377; +const int Configuration::MSER_DELTA = 3; // > the lesser this value, the more the detected regions +const int Configuration::MSER_MIN_AREA = Configuration::KP_MIN_SIZE * Configuration::KP_MIN_SIZE; +const int Configuration::MSER_MAX_AREA = Configuration::KP_MAX_SIZE * Configuration::KP_MAX_SIZE; +const double Configuration::MSER_MAX_VARIATION = 0.25; // > the more this value, the more the detected regions +const Scalar Configuration::MSER_COLOR(0, 255, 255); + +/* Annotation configuration. */ +const int Configuration::ANNOT_CTRL_PNT_SIZE = 3; +const int Configuration::ANNOT_CTRL_PNT_SIZE_SELECTED = 5; +const int Configuration::ANNOT_LINE_WIDTH = 1; +const int Configuration::ANNOT_LINE_WIDTH_SELECTED = 2; +const int Configuration::ANNOT_CTRL_PNT_DIST_TOLERANCE = 4; +const int Configuration::ANNOT_SELECTION_TOLERANCE = 5; +const int Configuration::ANNOT_CTRL_PNT_MIN_COUNT = 5; + +/* Software configuration. */ +// main window +const string Configuration::MAIN_WINDOW_TITLE = "TSHEPII"; +const int Configuration::MAIN_WINDOW_WIDTH = 1300; +const int Configuration::MAIN_WINDOW_HEIGHT = 800; +const Scalar Configuration::MAIN_WINDOW_BACKGROUND_COLOR(0, 0, 0); + +// main menu +const int Configuration::MAIN_MENU_X = 0; +const int Configuration::MAIN_MENU_Y = 0; +const int Configuration::MAIN_MENU_HEIGHT = 36; + +const string Configuration::MAIN_MENU_LOAD_IRIS_LABEL = "Load irises"; +const int Configuration::MAIN_MENU_LOAD_IRIS_WIDTH = 100; + +const string Configuration::MAIN_MENU_LOAD_EXAMINATION_LABEL = "Load examination"; +const int Configuration::MAIN_MENU_LOAD_EXAMINATION_WIDTH = 150; + +const string Configuration::MAIN_MENU_SAVE_EXAMINATION_LABEL = "Save examination"; +const int Configuration::MAIN_MENU_SAVE_EXAMINATION_WIDTH = 150; + +const string Configuration::MAIN_MENU_SAVE_REPORT_LABEL = "Save report"; +const int Configuration::MAIN_MENU_SAVE_REPORT_WIDTH = 120; + +const string Configuration::MAIN_MENU_QUIT_PROGRAM_LABEL = "Quit program"; +const int Configuration::MAIN_MENU_QUIT_PROGRAM_WIDTH = 120; +const string Configuration::MAIN_MENU_QUIT_PROGRAM_QUESTION = "Quit program?"; + +// modal messages +const int Configuration::MODAL_WIDTH = 300; +const int Configuration::MODAL_HEIGHT = 120; +const int Configuration::MODAL_BUTTON_HEIGHT = 40; +const string Configuration::MODAL_BUTTON_LABEL = "OK"; + +// yes/no dialog +const int Configuration::DIALOG_WIDTH = 300; +const int Configuration::DIALOG_HEIGHT = 120; +const int Configuration::DIALOG_BUTTON_HEIGHT = 40; +const string Configuration::DIALOG_YES_LABEL = "OK"; +const string Configuration::DIALOG_NO_LABEL = "Cancel"; + +// progress bar +const string Configuration::PROGRESS_TITLE = "Please wait"; +const int Configuration::PROGRESS_WIDTH = 300; +const int Configuration::PROGRESS_HEIGHT = 120; +const int Configuration::PROGRESS_BAR_HEIGHT = 40; +const int Configuration::PROGRESS_BAR_BORDER = 10; +const Scalar Configuration::PROGRESS_BAR_COLOR(255, 255, 255); + +// iris image loading +const char *Configuration::LOAD_IRIS_EXTENSIONS = "png,jpg,bmp,tif,tiff"; // valid extensions +const string Configuration::LOAD_IRIS_WARN_MSG_1 = "Please select two irises."; +const string Configuration::LOAD_IRIS_WARN_MSG_2 = "Could not open iris"; +const string Configuration::LOAD_IRIS_WARN_MSG_3 = "Loading irises..."; +const string Configuration::LOAD_IRIS_WARN_MSG_4 = "Could not compute OSIRIS score."; +const string Configuration::LOAD_IRIS_WARN_MSG_5 = "Could not compute BSIF score."; + +// examination loading +const string Configuration::SAVE_EXAM_WARN_MSG_1 = "Successfully saved current examination."; +const string Configuration::SAVE_EXAM_WARN_MSG_2 = "Could not save current examination."; +const string Configuration::LOAD_EXAM_WARN_MSG_1 = "Could not read stored examination."; + +// report saving +const string Configuration::SAVE_REPORT_WARN_MSG_1 = "Successfully saved report."; + +// iris windows +const int Configuration::IRIS_WIDTH = 384; +const int Configuration::IRIS_HEIGHT = 384; +const int Configuration::IRIS_RADIUS_OFFSET = 10; +const int Configuration::IRIS_1_X = 245; +const int Configuration::IRIS_1_Y = 36; +const int Configuration::IRIS_2_X = 671; +const int Configuration::IRIS_2_Y = 36; + +// iris image processing windows +const double Configuration::IRIS_BRIGHT_MIN = -1.0; +const double Configuration::IRIS_BRIGHT_MAX = 1.0; + +const double Configuration::IRIS_CONTRAST_MIN = 0.0; +const double Configuration::IRIS_CONTRAST_MAX = 2.0; + +const int Configuration::IRIS_SHARP_MIN = 0; +const int Configuration::IRIS_SHARP_MAX = 10; + +const string Configuration::IRIS_BRIGHT_TITLE = "Brightness"; +const string Configuration::IRIS_CONTRAST_TITLE = "Contrast"; +const string Configuration::IRIS_SHARP_TITLE = "Sharpening"; + +const int Configuration::IRIS_1_CTRL_WINDOW_X = 10; +const int Configuration::IRIS_1_CTRL_WINDOW_Y = 66; +const int Configuration::IRIS_1_CTRL_WINDOW_WIDTH = 225; +const int Configuration::IRIS_1_CTRL_WINDOW_HEIGHT = 355; + +const int Configuration::IRIS_1_BRIGHT_TITLE_X = 15; +const int Configuration::IRIS_1_BRIGHT_TITLE_Y = 100; +const int Configuration::IRIS_1_BRIGHT_TRACK_X = 15; +const int Configuration::IRIS_1_BRIGHT_TRACK_Y = 120; +const int Configuration::IRIS_1_BRIGHT_TRACK_WIDTH = 215; + +const int Configuration::IRIS_1_CONTRAST_TITLE_X = 15; +const int Configuration::IRIS_1_CONTRAST_TITLE_Y = 190; +const int Configuration::IRIS_1_CONTRAST_TRACK_X = 15; +const int Configuration::IRIS_1_CONTRAST_TRACK_Y = 210; +const int Configuration::IRIS_1_CONTRAST_TRACK_WIDTH = 215; + +const int Configuration::IRIS_1_SHARP_TITLE_X = 15; +const int Configuration::IRIS_1_SHARP_TITLE_Y = 280; +const int Configuration::IRIS_1_SHARP_TRACK_X = 15; +const int Configuration::IRIS_1_SHARP_TRACK_Y = 300; +const int Configuration::IRIS_1_SHARP_TRACK_WIDTH = 215; + +const string Configuration::IRIS_1_SEGMENT_BUTTON_LABEL = "Segment iris"; +const int Configuration::IRIS_1_SEGMENT_BUTTON_X = 25; +const int Configuration::IRIS_1_SEGMENT_BUTTON_Y = 370; +const int Configuration::IRIS_1_SEGMENT_BUTTON_WIDTH = 195; +const int Configuration::IRIS_1_SEGMENT_BUTTON_HEIGHT = 30; + +const int Configuration::IRIS_2_CTRL_WINDOW_X = 1065; +const int Configuration::IRIS_2_CTRL_WINDOW_Y = 66; +const int Configuration::IRIS_2_CTRL_WINDOW_WIDTH = 225; +const int Configuration::IRIS_2_CTRL_WINDOW_HEIGHT = 355; + +const int Configuration::IRIS_2_BRIGHT_TITLE_X = 1070; +const int Configuration::IRIS_2_BRIGHT_TITLE_Y = 100; +const int Configuration::IRIS_2_BRIGHT_TRACK_X = 1070; +const int Configuration::IRIS_2_BRIGHT_TRACK_Y = 120; +const int Configuration::IRIS_2_BRIGHT_TRACK_WIDTH = 215; + +const int Configuration::IRIS_2_CONTRAST_TITLE_X = 1070; +const int Configuration::IRIS_2_CONTRAST_TITLE_Y = 190; +const int Configuration::IRIS_2_CONTRAST_TRACK_X = 1070; +const int Configuration::IRIS_2_CONTRAST_TRACK_Y = 210; +const int Configuration::IRIS_2_CONTRAST_TRACK_WIDTH = 215; + +const int Configuration::IRIS_2_SHARP_TITLE_X = 1070; +const int Configuration::IRIS_2_SHARP_TITLE_Y = 280; +const int Configuration::IRIS_2_SHARP_TRACK_X = 1070; +const int Configuration::IRIS_2_SHARP_TRACK_Y = 300; +const int Configuration::IRIS_2_SHARP_TRACK_WIDTH = 215; + +const string Configuration::IRIS_2_SEGMENT_BUTTON_LABEL = "Segment iris"; +const int Configuration::IRIS_2_SEGMENT_BUTTON_X = 1080; +const int Configuration::IRIS_2_SEGMENT_BUTTON_Y = 370; +const int Configuration::IRIS_2_SEGMENT_BUTTON_WIDTH = 195; +const int Configuration::IRIS_2_SEGMENT_BUTTON_HEIGHT = 30; + +// iris zooming +const double Configuration::IRIS_ZOOM_MIN = 1.0; +const double Configuration::IRIS_ZOOM_MAX = 3.0; + +const int Configuration::IRIS_1_ZOOM_CTRL_WINDOW_X = 245; +const int Configuration::IRIS_1_ZOOM_CTRL_WINDOW_Y = 430; +const int Configuration::IRIS_1_ZOOM_CTRL_WINDOW_WIDTH = 374; +const int Configuration::IRIS_1_ZOOM_CTRL_WINDOW_HEIGHT = 60; + +const int Configuration::IRIS_2_ZOOM_CTRL_WINDOW_X = 681; +const int Configuration::IRIS_2_ZOOM_CTRL_WINDOW_Y = 430; +const int Configuration::IRIS_2_ZOOM_CTRL_WINDOW_WIDTH = 374; +const int Configuration::IRIS_2_ZOOM_CTRL_WINDOW_HEIGHT = 60; + +const int Configuration::IRIS_CONNECT_BTN_X = 620; +const int Configuration::IRIS_CONNECT_BTN_Y = 430; +const int Configuration::IRIS_CONNECT_BTN_WIDTH = 60; +const int Configuration::IRIS_CONNECT_BTN_HEIGHT = 60; +const string Configuration::IRIS_CONNECT_BTN_LABEL = "1x"; + +// human-interpretable and annotation windows: common features +const int Configuration::COLOR_FLAG_SIZE = 15; +const int Configuration::COLOR_FLAG_PADDING = 5; + +// non-human-interpretable output window +const string Configuration::NHI_OUTPUT_WINDOW_TITLE = "Non-Human-Interpretable Features"; +const int Configuration::NHI_OUTPUT_WINDOW_X = 10; +const int Configuration::NHI_OUTPUT_WINDOW_Y = 710; +const int Configuration::NHI_OUTPUT_WINDOW_WIDTH = 850; +const int Configuration::NHI_OUTPUT_WINDOW_HEIGHT = 80; + +const double Configuration::NHI_OUTPUT_SCORE_SIZE_1 = 0.6; +const double Configuration::NHI_OUTPUT_SCORE_SIZE_2 = 0.3; +const unsigned int Configuration::NHI_OUTPUT_SCORE_COLOR = 0xffffff; + +const string Configuration::NHI_OUTPUT_OSIRIS_LABEL_1 = "Gabor Filters"; +const string Configuration::NHI_OUTPUT_OSIRIS_LABEL_2 = "thr:"; +const int Configuration::NHI_OUTPUT_OSIRIS_LABEL_1_X = 30; +const int Configuration::NHI_OUTPUT_OSIRIS_LABEL_1_Y = 750; +const int Configuration::NHI_OUTPUT_OSIRIS_LABEL_2_X = 185; +const int Configuration::NHI_OUTPUT_OSIRIS_LABEL_2_Y = 767; +const int Configuration::NHI_OUTPUT_OSIRIS_SCORE_X = 180; +const int Configuration::NHI_OUTPUT_OSIRIS_SCORE_Y = 743; +const int Configuration::NHI_OUTPUT_OSIRIS_CLASS_X = 280; +const int Configuration::NHI_OUTPUT_OSIRIS_CLASS_Y = 750; + +const string Configuration::NHI_OUTPUT_BSIF_LABEL_1 = "BSIF Filters"; +const string Configuration::NHI_OUTPUT_BSIF_LABEL_2 = "thr:"; +const int Configuration::NHI_OUTPUT_BSIF_LABEL_1_X = 485; +const int Configuration::NHI_OUTPUT_BSIF_LABEL_1_Y = 750; +const int Configuration::NHI_OUTPUT_BSIF_LABEL_2_X = 625; +const int Configuration::NHI_OUTPUT_BSIF_LABEL_2_Y = 767; +const int Configuration::NHI_OUTPUT_BSIF_SCORE_X = 620; +const int Configuration::NHI_OUTPUT_BSIF_SCORE_Y = 743; +const int Configuration::NHI_OUTPUT_BSIF_CLASS_X = 720; +const int Configuration::NHI_OUTPUT_BSIF_CLASS_Y = 750; + +// manual annotation window +const string Configuration::ANNOT_CTRL_WINDOW_TITLE = "Manual Annotation"; +const int Configuration::ANNOT_CTRL_WINDOW_X = 870; +const int Configuration::ANNOT_CTRL_WINDOW_Y = 500; +const int Configuration::ANNOT_CTRL_WINDOW_WIDTH = 420; +const int Configuration::ANNOT_CTRL_WINDOW_HEIGHT = 200; + +const string Configuration::ANNOT_TYPE_CHKBOX_GROUP_LABEL = "Annotate..."; +const int Configuration::ANNOT_TYPE_CHKBOX_GROUP_X = 890; +const int Configuration::ANNOT_TYPE_CHKBOX_GROUP_Y = 540; + +const string Configuration::ANNOT_TYPE_CHKBOX_MATCH_LABEL = "Matching Regions"; +const int Configuration::ANNOT_TYPE_CHKBOX_MATCH_X = 890; +const int Configuration::ANNOT_TYPE_CHKBOX_MATCH_Y = 560; +const Scalar Configuration::ANNOT_TYPE_MATCH_COLOR(0, 255, 0); + +const string Configuration::ANNOT_TYPE_CHKBOX_NON_MATCH_LABEL = "Non-Matching Regions"; +const int Configuration::ANNOT_TYPE_CHKBOX_NON_MATCH_X = 1080; +const int Configuration::ANNOT_TYPE_CHKBOX_NON_MATCH_Y = 560; +const Scalar Configuration::ANNOT_TYPE_NON_MATCH_COLOR(0, 0, 255); + +const string Configuration::ANNOT_SHOW_MATCH_LABEL = "Show Matching Regions"; +const int Configuration::ANNOT_SHOW_MATCH_X = 890; +const int Configuration::ANNOT_SHOW_MATCH_Y = 620; + +const string Configuration::ANNOT_SHOW_NON_MATCH_LABEL = "Show Non-Matching Regions"; +const int Configuration::ANNOT_SHOW_NON_MATCH_X = 890; +const int Configuration::ANNOT_SHOW_NON_MATCH_Y = 640; + +// human-interpretable feature window +const int Configuration::HI_INITIAL_MATCH_COUNT = 5; +const int Configuration::HI_INITIAL_NON_MATCH_COUNT = 1; +const double Configuration::HI_SCORE_LABEL_SIZE = 0.3; + +const string Configuration::HI_FEAT_CTRL_WINDOW_TITLE = "Human-Interpretable Features"; +const int Configuration::HI_FEAT_CTRL_WINDOW_X = 10; +const int Configuration::HI_FEAT_CTRL_WINDOW_Y = 500; +const int Configuration::HI_FEAT_CTRL_WINDOW_WIDTH = 850; +const int Configuration::HI_FEAT_CTRL_WINDOW_HEIGHT = 200; +const string Configuration::HI_FEAT_CHKBOX_MATCH_LABEL = "Show Matched"; +const string Configuration::HI_FEAT_CHKBOX_NON_MATCH_LABEL = "Show Unmatched"; + +const string Configuration::HI_FEAT_TSHEPII_LABEL = "TSHEPII"; +const int Configuration::HI_FEAT_TSHEPII_LABEL_X = 30; +const int Configuration::HI_FEAT_TSHEPII_LABEL_Y = 540; +const int Configuration::HI_FEAT_TSHEPII_CLASS_X = 30; +const int Configuration::HI_FEAT_TSHEPII_CLASS_Y = 562; +const int Configuration::HI_FEAT_TSHEPII_CHKBOX_MATCH_X = 110; +const int Configuration::HI_FEAT_TSHEPII_CHKBOX_MATCH_Y = 540; +const int Configuration::HI_FEAT_TSHEPII_CHKBOX_NON_MATCH_X = 110; +const int Configuration::HI_FEAT_TSHEPII_CHKBOX_NON_MATCH_Y = 560; +const int Configuration::HI_FEAT_TSHEPII_COUNT_MATCH_X = 255; +const int Configuration::HI_FEAT_TSHEPII_COUNT_MATCH_Y = 540; +const int Configuration::HI_FEAT_TSHEPII_COUNT_NON_MATCH_X = 255; +const int Configuration::HI_FEAT_TSHEPII_COUNT_NON_MATCH_Y = 560; +const int Configuration::HI_FEAT_TSHEPII_MAX_MATCH_X = 355; +const int Configuration::HI_FEAT_TSHEPII_MAX_MATCH_Y = 545; +const int Configuration::HI_FEAT_TSHEPII_MAX_NON_MATCH_X = 355; +const int Configuration::HI_FEAT_TSHEPII_MAX_NON_MATCH_Y = 565; +const Scalar Configuration::HI_FEAT_TSHEPII_COLOR(255, 0, 0); + +const string Configuration::HI_FEAT_SURF_LABEL = "SURF"; +const int Configuration::HI_FEAT_SURF_LABEL_X = 30; +const int Configuration::HI_FEAT_SURF_LABEL_Y = 600; +const int Configuration::HI_FEAT_SURF_CLASS_X = 30; +const int Configuration::HI_FEAT_SURF_CLASS_Y = 622; +const int Configuration::HI_FEAT_SURF_CHKBOX_MATCH_X = 110; +const int Configuration::HI_FEAT_SURF_CHKBOX_MATCH_Y = 600; +const int Configuration::HI_FEAT_SURF_CHKBOX_NON_MATCH_X = 110; +const int Configuration::HI_FEAT_SURF_CHKBOX_NON_MATCH_Y = 620; +const int Configuration::HI_FEAT_SURF_COUNT_MATCH_X = 255; +const int Configuration::HI_FEAT_SURF_COUNT_MATCH_Y = 600; +const int Configuration::HI_FEAT_SURF_COUNT_NON_MATCH_X = 255; +const int Configuration::HI_FEAT_SURF_COUNT_NON_MATCH_Y = 620; +const int Configuration::HI_FEAT_SURF_MAX_MATCH_X = 355; +const int Configuration::HI_FEAT_SURF_MAX_MATCH_Y = 605; +const int Configuration::HI_FEAT_SURF_MAX_NON_MATCH_X = 355; +const int Configuration::HI_FEAT_SURF_MAX_NON_MATCH_Y = 625; +const Scalar Configuration::HI_FEAT_SURF_COLOR(0, 130, 255); + +const string Configuration::HI_FEAT_SIFT_LABEL = "SIFT"; +const int Configuration::HI_FEAT_SIFT_LABEL_X = 460; +const int Configuration::HI_FEAT_SIFT_LABEL_Y = 600; +const int Configuration::HI_FEAT_SIFT_CLASS_X = 460; +const int Configuration::HI_FEAT_SIFT_CLASS_Y = 622; +const int Configuration::HI_FEAT_SIFT_CHKBOX_MATCH_X = 530; +const int Configuration::HI_FEAT_SIFT_CHKBOX_MATCH_Y = 600; +const int Configuration::HI_FEAT_SIFT_CHKBOX_NON_MATCH_X = 530; +const int Configuration::HI_FEAT_SIFT_CHKBOX_NON_MATCH_Y = 620; +const int Configuration::HI_FEAT_SIFT_COUNT_MATCH_X = 675; +const int Configuration::HI_FEAT_SIFT_COUNT_MATCH_Y = 600; +const int Configuration::HI_FEAT_SIFT_COUNT_NON_MATCH_X = 675; +const int Configuration::HI_FEAT_SIFT_COUNT_NON_MATCH_Y = 620; +const int Configuration::HI_FEAT_SIFT_MAX_MATCH_X = 775; +const int Configuration::HI_FEAT_SIFT_MAX_MATCH_Y = 605; +const int Configuration::HI_FEAT_SIFT_MAX_NON_MATCH_X = 775; +const int Configuration::HI_FEAT_SIFT_MAX_NON_MATCH_Y = 625; +const Scalar Configuration::HI_FEAT_SIFT_COLOR(255, 0, 255); + +const string Configuration::HI_FEAT_MSER_LABEL = "MSER"; +const int Configuration::HI_FEAT_MSER_LABEL_X = 460; +const int Configuration::HI_FEAT_MSER_LABEL_Y = 540; +const int Configuration::HI_FEAT_MSER_CLASS_X = 460; +const int Configuration::HI_FEAT_MSER_CLASS_Y = 562; +const int Configuration::HI_FEAT_MSER_CHKBOX_MATCH_X = 530; +const int Configuration::HI_FEAT_MSER_CHKBOX_MATCH_Y = 540; +const int Configuration::HI_FEAT_MSER_CHKBOX_NON_MATCH_X = 530; +const int Configuration::HI_FEAT_MSER_CHKBOX_NON_MATCH_Y = 560; +const int Configuration::HI_FEAT_MSER_COUNT_MATCH_X = 675; +const int Configuration::HI_FEAT_MSER_COUNT_MATCH_Y = 540; +const int Configuration::HI_FEAT_MSER_COUNT_NON_MATCH_X = 675; +const int Configuration::HI_FEAT_MSER_COUNT_NON_MATCH_Y = 560; +const int Configuration::HI_FEAT_MSER_MAX_MATCH_X = 775; +const int Configuration::HI_FEAT_MSER_MAX_MATCH_Y = 545; +const int Configuration::HI_FEAT_MSER_MAX_NON_MATCH_X = 775; +const int Configuration::HI_FEAT_MSER_MAX_NON_MATCH_Y = 565; +const Scalar Configuration::HI_FEAT_MSER_COLOR(0, 255, 255); + +const string Configuration::HI_FEAT_CRYPTS_LABEL = "Crypts"; +const int Configuration::HI_FEAT_CRYPTS_LABEL_X = 30; +const int Configuration::HI_FEAT_CRYPTS_LABEL_Y = 660; +const int Configuration::HI_FEAT_CRYPTS_CLASS_X = 30; +const int Configuration::HI_FEAT_CRYPTS_CLASS_Y = 682; +const int Configuration::HI_FEAT_CRYPTS_CHKBOX_MATCH_X = 110; +const int Configuration::HI_FEAT_CRYPTS_CHKBOX_MATCH_Y = 660; +const int Configuration::HI_FEAT_CRYPTS_COUNT_MATCH_X = 255; +const int Configuration::HI_FEAT_CRYPTS_COUNT_MATCH_Y = 660; +const int Configuration::HI_FEAT_CRYPTS_MAX_MATCH_X = 355; +const int Configuration::HI_FEAT_CRYPTS_MAX_MATCH_Y = 660; +const Scalar Configuration::HI_FEAT_CRYPTS_COLOR(255, 255, 0); + +const int Configuration::HI_UNDO_REMOVAL_BUTTON_X = 675; +const int Configuration::HI_UNDO_REMOVAL_BUTTON_Y = 660; +const int Configuration::HI_UNDO_REMOVAL_BUTTON_WIDTH = 160; +const int Configuration::HI_UNDO_REMOVAL_BUTTON_HEIGHT = 30; +const string Configuration::HI_UNDO_REMOVAL_BUTTON_LABEL = "Undo last removal"; + +// score window +const string Configuration::SCORE_WINDOW_TITLE = "Global match score"; +const int Configuration::SCORE_WINDOW_X = 870; +const int Configuration::SCORE_WINDOW_Y = 710; +const int Configuration::SCORE_WINDOW_WIDTH = 420; +const int Configuration::SCORE_WINDOW_HEIGHT = 80; +const double Configuration::SCORE_LABEL_SIZE = 0.6; +const int Configuration::SCORE_LABEL_X = 950; +const int Configuration::SCORE_LABEL_Y = 750; +const double Configuration::SCORE_VALUE_SIZE = 0.6; +const int Configuration::SCORE_VALUE_X = 1150; +const int Configuration::SCORE_VALUE_Y = 750; +const unsigned int Configuration::SCORE_VALUE_COLOR = 0xffffff; +const string Configuration::SCORE_GENUINE_LABEL = "genuine"; +const unsigned int Configuration::SCORE_GENUINE_COLOR = 0xffffff; +const string Configuration::SCORE_IMPOSTOR_LABEL = "impostor"; +const unsigned int Configuration::SCORE_IMPOSTOR_COLOR = 0xffffff; + +// remove-annotation popup window +const string Configuration::ANNOT_REMOVAL_POPUP_TITLE = "Annotation"; +const int Configuration::ANNOT_REMOVAL_POPUP_WIDTH = 100; +const int Configuration::ANNOT_REMOVAL_POPUP_HEIGHT = 60; +const string Configuration::ANNOT_REMOVAL_POPUP_REMOVE_OP = "Remove"; +const string Configuration::ANNOT_REMOVAL_POPUP_CANCEL_OP = "Cancel"; + +// remove-keypoint popup window +const string Configuration::KP_REMOVAL_POPUP_TITLE = "Keypoint"; +const int Configuration::KP_REMOVAL_POPUP_WIDTH = 100; +const int Configuration::KP_REMOVAL_POPUP_HEIGHT = 60; +const string Configuration::KP_REMOVAL_POPUP_REMOVE_OP = "Remove"; +const string Configuration::KP_REMOVAL_POPUP_CANCEL_OP = "Cancel"; + +// report content +const string Configuration::REPORT_TITLE = "TSHEPII Report"; + +const string Configuration::REPORT_HEADER_1 = "Examined Irises"; +const string Configuration::REPORT_TABLE_1_HEADER_1 = "Iris 1"; +const string Configuration::REPORT_TABLE_1_HEADER_2 = "Iris 2"; + +const string Configuration::REPORT_HEADER_2 = "Annotations"; +const string Configuration::REPORT_TABLE_2_HEADER_1 = "Matched (#)"; +const string Configuration::REPORT_TABLE_2_HEADER_2 = "Unmatched (#)"; + +const string Configuration::REPORT_HEADER_3 = "TSHEPII Descriptor Matches"; +const string Configuration::REPORT_TABLE_3_HEADER_1 = "Matched (#)"; +const string Configuration::REPORT_TABLE_3_HEADER_2 = "Unmatched (#)"; + +const string Configuration::REPORT_HEADER_4 = "SURF Matches"; +const string Configuration::REPORT_TABLE_4_HEADER_1 = "Matched (#)"; +const string Configuration::REPORT_TABLE_4_HEADER_2 = "Unmatched (#)"; + +const string Configuration::REPORT_HEADER_5 = "SIFT Matches"; +const string Configuration::REPORT_TABLE_5_HEADER_1 = "Matched (#)"; +const string Configuration::REPORT_TABLE_5_HEADER_2 = "Unmatched (#)"; + +const string Configuration::REPORT_HEADER_6 = "MSER Matches"; +const string Configuration::REPORT_TABLE_6_HEADER_1 = "Matched (#)"; +const string Configuration::REPORT_TABLE_6_HEADER_2 = "Unmatched (#)"; + +const string Configuration::REPORT_HEADER_7 = "Crypt Matches"; +const string Configuration::REPORT_TABLE_7_HEADER_1 = "Matched (#)"; + +const string Configuration::REPORT_HEADER_8 = "Scores"; +const string Configuration::REPORT_TABLE_8_HEADER_1 = "Method"; +const string Configuration::REPORT_TABLE_8_HEADER_2 = "Threshold"; +const string Configuration::REPORT_TABLE_8_HEADER_3 = "Score"; +const string Configuration::REPORT_TABLE_8_HEADER_4 = "Genuine?"; +const string Configuration::REPORT_TABLE_8_COLUMN_1_ROW_01 = "Gabor Filters"; +const string Configuration::REPORT_TABLE_8_COLUMN_1_ROW_02 = "BSIF Filters"; +const string Configuration::REPORT_TABLE_8_COLUMN_1_ROW_03 = "TSHEPII Matched"; +const string Configuration::REPORT_TABLE_8_COLUMN_1_ROW_04 = "TSHEPII Unmatched"; +const string Configuration::REPORT_TABLE_8_COLUMN_1_ROW_05 = "SURF Matched"; +const string Configuration::REPORT_TABLE_8_COLUMN_1_ROW_06 = "SURF Unmatched"; +const string Configuration::REPORT_TABLE_8_COLUMN_1_ROW_07 = "SIFT Matched"; +const string Configuration::REPORT_TABLE_8_COLUMN_1_ROW_08 = "SIFT Unmatched"; +const string Configuration::REPORT_TABLE_8_COLUMN_1_ROW_09 = "MSER Matched"; +const string Configuration::REPORT_TABLE_8_COLUMN_1_ROW_10 = "MSER Unmatched"; +const string Configuration::REPORT_TABLE_8_COLUMN_1_ROW_11 = "Crypt Matched"; +const string Configuration::REPORT_TABLE_8_COLUMN_1_ROW_12 = "Combined"; + +const string Configuration::REPORT_GENUINE_LABEL = "Yes"; +const string Configuration::REPORT_IMPOSTOR_LABEL = "No"; + +// manual iris segmentation window +const string Configuration::SEGMENT_WINDOW_TITLE = "Segment"; +const int Configuration::SEGMENT_WINDOW_BOTTOM_HEIGHT = 60; +const int Configuration::SEGMENT_WINDOW_HORIZ_PADDING = 5; +const int Configuration::SEGMENT_WINDOW_VERTC_PADDING = 25; +const int Configuration::SEGMENT_IRIS_HEIGHT = 500; +const string Configuration::SEGMENT_MESSAGE_1 = "Select 3 or more points over the pupil border."; +const string Configuration::SEGMENT_MESSAGE_2 = "Select 3 or more points over the iris border."; +const string Configuration::SEGMENT_MESSAGE_3 = "Draw on the border of region to remove."; +const string Configuration::SEGMENT_BUTTON_LABEL_1 = "Next step"; +const string Configuration::SEGMENT_BUTTON_LABEL_2 = "Restart step"; +const string Configuration::SEGMENT_BUTTON_LABEL_3 = "Cancel segmentation"; +const string Configuration::SEGMENT_BUTTON_LABEL_4 = "Previous step"; +const int Configuration::SEGMENT_POINT_SIZE = 3; +const int Configuration::SEGMENT_LINE_WIDTH = 1; +const int Configuration::SEGMENT_CTRL_PNT_DIST_TOLERANCE = 7; +const Scalar Configuration::SEGMENT_PUPIL_COLOR(0, 255, 255); +const Scalar Configuration::SEGMENT_IRIS_COLOR(255, 0, 0); +const Scalar Configuration::SEGMENT_REGION_COLOR(0, 0, 255); +const Scalar Configuration::SEGMENT_CLOSED_REGION_COLOR(0, 255, 0); diff --git a/tshepii/src/Configuration.hpp b/tshepii/src/Configuration.hpp new file mode 100755 index 0000000..9572e1a --- /dev/null +++ b/tshepii/src/Configuration.hpp @@ -0,0 +1,561 @@ +/************************************************************************* +* TSHEPII: Tool for Supporting Human Examination of Postmortem Iris Images +* Version : 1.0 +* Date : April 2018 +* Authors : Daniel Moreira, Adam Czajka, University of Notre Dame, USA +*************************************************************************/ + +#ifndef CONFIG_H +#define CONFIG_H + +#include + +using namespace std; +using namespace cv; + +/** Singleton for loading TSHEPII configuration values. */ +class Configuration { +public: + /* General configuration. */ + static const string FILE_SEPARATOR; + static const int PROCESSED_IRIS_IMAGE_COUNT; + static const int SCORE_COUNT; + + /* OSIRIS configuration. */ + static const double OSIRIS_THRESHOLD; + static int OSIRIS_MIN_IRIS_DIAMETER; + static int OSIRIS_MIN_PUPIL_DIAMETER; + static int OSIRIS_MAX_IRIS_DIAMETER; + static int OSIRIS_MAX_PUPIL_DIAMETER; + static int OSIRIS_NORM_IMAGE_COLS; + static int OSIRIS_NORM_IMAGE_ROWS; + + /* BSIF configuration. */ + static const double BSIF_THRESHOLD; + static const int BSIF_NORM_IMAGE_COLS; + static const int BSIF_NORM_IMAGE_ROWS; + + /* Iris content pre-processing. */ + static const int IRIS_MASK_BLUR_SIZE; + static const int IRIS_CLAHE_TILES; + static const double IRIS_CLAHE_CLIP; + + /* Interest point configuration. */ + // All detectors + static const int KP_LINE_WIDTH; + static const int KP_MIN_SIZE; + static const int KP_MAX_SIZE; + static const double KP_COINCIDENCE_TOLERANCE; + static const int KP_MATCHER_KNN_K; + static const double KP_MATCHER_NORM_L2_QUALITY_RATIO; + static const double KP_MATCHER_NORM_HAMMING_QUALITY_RATIO; + static const double KP_MATCHER_DEVIATION_TOLERANCE; + static const double KP_MATCHER_POLAR_RADIUS_TOLERANCE; + static const double KP_MATCHER_POLAR_ANGLE_TOLERANCE; + + // Crypts + static const double CRYPT_THRESHOLD; + static const Scalar CRYPTS_COLOR; + static const int CRYPTS_LINE_WIDTH; + static const int CRYPTS_SMOOTH_GAUSS_SIZE; + static const int CRYPTS_SMOOTH_GAUSS_SIGMA; + static const int CRYPTS_MIN_ITERATIVE_STR_EL_SIZE; + static const int CRYPTS_INITIAL_ITERATIVE_STR_EL_SIZE; + static const double CRYPTS_MIN_CRYPT_AREA; + static const double CRYPTS_MAX_CRYP_AREA_LB; + static const double CRYPTS_MIN_CRYP_AREA_UB; + static const double CRYPTS_CUT_STD; + static const double CRYPTS_MATCH_COINCIDENCE_TOLERANCE; + static const double CRYPTS_MATCH_DIST_ALPHA; + + // TSHEPII + static const double TSHEPII_MATCH_THRESHOLD; + static const double TSHEPII_MISMATCH_THRESHOLD; + static const double TSHEPII_BORDER_AVOIDANCE; + static const double TSHEPII_OVERLAP_TOLERANCE; + static const double TSHEPII_KP_THRESHOLD; + static const int TSHEPII_CELL_DIVISION; + static const double TSHEPII_BIN_THRESHOLD; + static const string TSHEPII_FILTER_LIST_FILEPATH; + static const string TSHEPII_ZND_LIST_FILEPATH; + static const Scalar TSHEPII_COLOR; + + // SURF + static const double SURF_MATCH_THRESHOLD; + static const double SURF_MISMATCH_THRESHOLD; + static const double SURF_HESSIAN_THRESH; + static const Scalar SURF_COLOR; + + // SIFT + static const double SIFT_MATCH_THRESHOLD; + static const double SIFT_MISMATCH_THRESHOLD; + static const int SIFT_KP_COUNT; + static const int SIFT_OCTAVE_LAYER_COUNT; + static const double SIFT_CONTRAST_THRESH; + static const double SIFT_EDGE_THRESH; + static const Scalar SIFT_COLOR; + + // MSER + static const double MSER_MATCH_THRESHOLD; + static const double MSER_MISMATCH_THRESHOLD; + static const int MSER_DELTA; + static const int MSER_MIN_AREA; + static const int MSER_MAX_AREA; + static const double MSER_MAX_VARIATION; + static const Scalar MSER_COLOR; + + /* Annotation configuration. */ + static const int ANNOT_CTRL_PNT_SIZE; + static const int ANNOT_CTRL_PNT_SIZE_SELECTED; + static const int ANNOT_CTRL_PNT_DIST_TOLERANCE; + static const int ANNOT_LINE_WIDTH; + static const int ANNOT_LINE_WIDTH_SELECTED; + static const int ANNOT_SELECTION_TOLERANCE; + static const int ANNOT_CTRL_PNT_MIN_COUNT; + + /* Software static configuration. */ + // main window + static const string MAIN_WINDOW_TITLE; + static const int MAIN_WINDOW_WIDTH; + static const int MAIN_WINDOW_HEIGHT; + static const Scalar MAIN_WINDOW_BACKGROUND_COLOR; + + // main menu + static const int MAIN_MENU_X; + static const int MAIN_MENU_Y; + static const int MAIN_MENU_HEIGHT; + + static const string MAIN_MENU_LOAD_IRIS_LABEL; + static const int MAIN_MENU_LOAD_IRIS_WIDTH; + + static const string MAIN_MENU_LOAD_EXAMINATION_LABEL; + static const int MAIN_MENU_LOAD_EXAMINATION_WIDTH; + + static const string MAIN_MENU_SAVE_EXAMINATION_LABEL; + static const int MAIN_MENU_SAVE_EXAMINATION_WIDTH; + + static const string MAIN_MENU_SAVE_REPORT_LABEL; + static const int MAIN_MENU_SAVE_REPORT_WIDTH; + + static const string MAIN_MENU_QUIT_PROGRAM_LABEL; + static const int MAIN_MENU_QUIT_PROGRAM_WIDTH; + static const string MAIN_MENU_QUIT_PROGRAM_QUESTION; + + // modal messages + static const int MODAL_WIDTH; + static const int MODAL_HEIGHT; + static const int MODAL_BUTTON_HEIGHT; + static const string MODAL_BUTTON_LABEL; + + // yes/no dialog + static const int DIALOG_WIDTH; + static const int DIALOG_HEIGHT; + static const int DIALOG_BUTTON_HEIGHT; + static const string DIALOG_YES_LABEL; + static const string DIALOG_NO_LABEL; + + // progress bar + static const string PROGRESS_TITLE; + static const int PROGRESS_WIDTH; + static const int PROGRESS_HEIGHT; + static const int PROGRESS_BAR_HEIGHT; + static const int PROGRESS_BAR_BORDER; + static const Scalar PROGRESS_BAR_COLOR; + + // iris image loading + static const char *LOAD_IRIS_EXTENSIONS; + static const string LOAD_IRIS_WARN_MSG_1; + static const string LOAD_IRIS_WARN_MSG_2; + static const string LOAD_IRIS_WARN_MSG_3; + static const string LOAD_IRIS_WARN_MSG_4; + static const string LOAD_IRIS_WARN_MSG_5; + + // examination saving and loading + static const string SAVE_EXAM_WARN_MSG_1; + static const string SAVE_EXAM_WARN_MSG_2; + static const string LOAD_EXAM_WARN_MSG_1; + + // report saving + static const string SAVE_REPORT_WARN_MSG_1; + + // iris windows + static const int IRIS_WIDTH; + static const int IRIS_HEIGHT; + static const int IRIS_RADIUS_OFFSET; + static const int IRIS_1_X; + static const int IRIS_1_Y; + static const int IRIS_2_X; + static const int IRIS_2_Y; + + // iris image processing windows + static const double IRIS_BRIGHT_MIN; + static const double IRIS_BRIGHT_MAX; + + static const double IRIS_CONTRAST_MIN; + static const double IRIS_CONTRAST_MAX; + + static const int IRIS_SHARP_MIN; + static const int IRIS_SHARP_MAX; + + static const string IRIS_BRIGHT_TITLE; + static const string IRIS_CONTRAST_TITLE; + static const string IRIS_SHARP_TITLE; + + static const int IRIS_1_CTRL_WINDOW_X; + static const int IRIS_1_CTRL_WINDOW_Y; + static const int IRIS_1_CTRL_WINDOW_WIDTH; + static const int IRIS_1_CTRL_WINDOW_HEIGHT; + + static const int IRIS_1_BRIGHT_TITLE_X; + static const int IRIS_1_BRIGHT_TITLE_Y; + static const int IRIS_1_BRIGHT_TRACK_X; + static const int IRIS_1_BRIGHT_TRACK_Y; + static const int IRIS_1_BRIGHT_TRACK_WIDTH; + + static const int IRIS_1_CONTRAST_TITLE_X; + static const int IRIS_1_CONTRAST_TITLE_Y; + static const int IRIS_1_CONTRAST_TRACK_X; + static const int IRIS_1_CONTRAST_TRACK_Y; + static const int IRIS_1_CONTRAST_TRACK_WIDTH; + + static const int IRIS_1_SHARP_TITLE_X; + static const int IRIS_1_SHARP_TITLE_Y; + static const int IRIS_1_SHARP_TRACK_X; + static const int IRIS_1_SHARP_TRACK_Y; + static const int IRIS_1_SHARP_TRACK_WIDTH; + + static const string IRIS_1_SEGMENT_BUTTON_LABEL; + static const int IRIS_1_SEGMENT_BUTTON_X; + static const int IRIS_1_SEGMENT_BUTTON_Y; + static const int IRIS_1_SEGMENT_BUTTON_WIDTH; + static const int IRIS_1_SEGMENT_BUTTON_HEIGHT; + + static const int IRIS_2_CTRL_WINDOW_X; + static const int IRIS_2_CTRL_WINDOW_Y; + static const int IRIS_2_CTRL_WINDOW_WIDTH; + static const int IRIS_2_CTRL_WINDOW_HEIGHT; + + static const int IRIS_2_BRIGHT_TITLE_X; + static const int IRIS_2_BRIGHT_TITLE_Y; + static const int IRIS_2_BRIGHT_TRACK_X; + static const int IRIS_2_BRIGHT_TRACK_Y; + static const int IRIS_2_BRIGHT_TRACK_WIDTH; + + static const int IRIS_2_CONTRAST_TITLE_X; + static const int IRIS_2_CONTRAST_TITLE_Y; + static const int IRIS_2_CONTRAST_TRACK_X; + static const int IRIS_2_CONTRAST_TRACK_Y; + static const int IRIS_2_CONTRAST_TRACK_WIDTH; + + static const int IRIS_2_SHARP_TITLE_X; + static const int IRIS_2_SHARP_TITLE_Y; + static const int IRIS_2_SHARP_TRACK_X; + static const int IRIS_2_SHARP_TRACK_Y; + static const int IRIS_2_SHARP_TRACK_WIDTH; + + static const string IRIS_2_SEGMENT_BUTTON_LABEL; + static const int IRIS_2_SEGMENT_BUTTON_X; + static const int IRIS_2_SEGMENT_BUTTON_Y; + static const int IRIS_2_SEGMENT_BUTTON_WIDTH; + static const int IRIS_2_SEGMENT_BUTTON_HEIGHT; + + // iris zooming + static const double IRIS_ZOOM_MIN; + static const double IRIS_ZOOM_MAX; + + static const int IRIS_1_ZOOM_CTRL_WINDOW_X; + static const int IRIS_1_ZOOM_CTRL_WINDOW_Y; + static const int IRIS_1_ZOOM_CTRL_WINDOW_WIDTH; + static const int IRIS_1_ZOOM_CTRL_WINDOW_HEIGHT; + + static const int IRIS_2_ZOOM_CTRL_WINDOW_X; + static const int IRIS_2_ZOOM_CTRL_WINDOW_Y; + static const int IRIS_2_ZOOM_CTRL_WINDOW_WIDTH; + static const int IRIS_2_ZOOM_CTRL_WINDOW_HEIGHT; + + static const int IRIS_CONNECT_BTN_X; + static const int IRIS_CONNECT_BTN_Y; + static const int IRIS_CONNECT_BTN_WIDTH; + static const int IRIS_CONNECT_BTN_HEIGHT; + static const string IRIS_CONNECT_BTN_LABEL; + + // human-interpretable and annotation windows: common features + static const int COLOR_FLAG_SIZE; + static const int COLOR_FLAG_PADDING; + + // non-human-interpretable output window + static const string NHI_OUTPUT_WINDOW_TITLE; + static const int NHI_OUTPUT_WINDOW_X; + static const int NHI_OUTPUT_WINDOW_Y; + static const int NHI_OUTPUT_WINDOW_WIDTH; + static const int NHI_OUTPUT_WINDOW_HEIGHT; + + static const double NHI_OUTPUT_SCORE_SIZE_1; + static const double NHI_OUTPUT_SCORE_SIZE_2; + static const unsigned int NHI_OUTPUT_SCORE_COLOR; + + static const string NHI_OUTPUT_OSIRIS_LABEL_1; + static const string NHI_OUTPUT_OSIRIS_LABEL_2; + static const int NHI_OUTPUT_OSIRIS_LABEL_1_X; + static const int NHI_OUTPUT_OSIRIS_LABEL_1_Y; + static const int NHI_OUTPUT_OSIRIS_LABEL_2_X; + static const int NHI_OUTPUT_OSIRIS_LABEL_2_Y; + static const int NHI_OUTPUT_OSIRIS_SCORE_X; + static const int NHI_OUTPUT_OSIRIS_SCORE_Y; + static const int NHI_OUTPUT_OSIRIS_CLASS_X; + static const int NHI_OUTPUT_OSIRIS_CLASS_Y; + + static const string NHI_OUTPUT_BSIF_LABEL_1; + static const string NHI_OUTPUT_BSIF_LABEL_2; + static const int NHI_OUTPUT_BSIF_LABEL_1_X; + static const int NHI_OUTPUT_BSIF_LABEL_1_Y; + static const int NHI_OUTPUT_BSIF_LABEL_2_X; + static const int NHI_OUTPUT_BSIF_LABEL_2_Y; + static const int NHI_OUTPUT_BSIF_SCORE_X; + static const int NHI_OUTPUT_BSIF_SCORE_Y; + static const int NHI_OUTPUT_BSIF_CLASS_X; + static const int NHI_OUTPUT_BSIF_CLASS_Y; + + // manual annotation window + static const string ANNOT_CTRL_WINDOW_TITLE; + static const int ANNOT_CTRL_WINDOW_X; + static const int ANNOT_CTRL_WINDOW_Y; + static const int ANNOT_CTRL_WINDOW_WIDTH; + static const int ANNOT_CTRL_WINDOW_HEIGHT; + + static const string ANNOT_TYPE_CHKBOX_GROUP_LABEL; + static const int ANNOT_TYPE_CHKBOX_GROUP_X; + static const int ANNOT_TYPE_CHKBOX_GROUP_Y; + + static const string ANNOT_TYPE_CHKBOX_MATCH_LABEL; + static const int ANNOT_TYPE_CHKBOX_MATCH_X; + static const int ANNOT_TYPE_CHKBOX_MATCH_Y; + static const Scalar ANNOT_TYPE_MATCH_COLOR; + + static const string ANNOT_TYPE_CHKBOX_NON_MATCH_LABEL; + static const int ANNOT_TYPE_CHKBOX_NON_MATCH_X; + static const int ANNOT_TYPE_CHKBOX_NON_MATCH_Y; + static const Scalar ANNOT_TYPE_NON_MATCH_COLOR; + + static const string ANNOT_SHOW_MATCH_LABEL; + static const int ANNOT_SHOW_MATCH_X; + static const int ANNOT_SHOW_MATCH_Y; + + static const string ANNOT_SHOW_NON_MATCH_LABEL; + static const int ANNOT_SHOW_NON_MATCH_X; + static const int ANNOT_SHOW_NON_MATCH_Y; + + // human-interpretable feature window + static const int HI_INITIAL_MATCH_COUNT; + static const int HI_INITIAL_NON_MATCH_COUNT; + static const double HI_SCORE_LABEL_SIZE; + + static const string HI_FEAT_CTRL_WINDOW_TITLE; + static const int HI_FEAT_CTRL_WINDOW_X; + static const int HI_FEAT_CTRL_WINDOW_Y; + static const int HI_FEAT_CTRL_WINDOW_WIDTH; + static const int HI_FEAT_CTRL_WINDOW_HEIGHT; + static const string HI_FEAT_CHKBOX_MATCH_LABEL; + static const string HI_FEAT_CHKBOX_NON_MATCH_LABEL; + + static const string HI_FEAT_TSHEPII_LABEL; + static const int HI_FEAT_TSHEPII_LABEL_X; + static const int HI_FEAT_TSHEPII_LABEL_Y; + static const int HI_FEAT_TSHEPII_CLASS_X; + static const int HI_FEAT_TSHEPII_CLASS_Y; + static const int HI_FEAT_TSHEPII_CHKBOX_MATCH_X; + static const int HI_FEAT_TSHEPII_CHKBOX_MATCH_Y; + static const int HI_FEAT_TSHEPII_CHKBOX_NON_MATCH_X; + static const int HI_FEAT_TSHEPII_CHKBOX_NON_MATCH_Y; + static const int HI_FEAT_TSHEPII_COUNT_MATCH_X; + static const int HI_FEAT_TSHEPII_COUNT_MATCH_Y; + static const int HI_FEAT_TSHEPII_COUNT_NON_MATCH_X; + static const int HI_FEAT_TSHEPII_COUNT_NON_MATCH_Y; + static const int HI_FEAT_TSHEPII_MAX_MATCH_X; + static const int HI_FEAT_TSHEPII_MAX_MATCH_Y; + static const int HI_FEAT_TSHEPII_MAX_NON_MATCH_X; + static const int HI_FEAT_TSHEPII_MAX_NON_MATCH_Y; + static const Scalar HI_FEAT_TSHEPII_COLOR; + + static const string HI_FEAT_SURF_LABEL; + static const int HI_FEAT_SURF_LABEL_X; + static const int HI_FEAT_SURF_LABEL_Y; + static const int HI_FEAT_SURF_CLASS_X; + static const int HI_FEAT_SURF_CLASS_Y; + static const int HI_FEAT_SURF_CHKBOX_MATCH_X; + static const int HI_FEAT_SURF_CHKBOX_MATCH_Y; + static const int HI_FEAT_SURF_CHKBOX_NON_MATCH_X; + static const int HI_FEAT_SURF_CHKBOX_NON_MATCH_Y; + static const int HI_FEAT_SURF_COUNT_MATCH_X; + static const int HI_FEAT_SURF_COUNT_MATCH_Y; + static const int HI_FEAT_SURF_COUNT_NON_MATCH_X; + static const int HI_FEAT_SURF_COUNT_NON_MATCH_Y; + static const int HI_FEAT_SURF_MAX_MATCH_X; + static const int HI_FEAT_SURF_MAX_MATCH_Y; + static const int HI_FEAT_SURF_MAX_NON_MATCH_X; + static const int HI_FEAT_SURF_MAX_NON_MATCH_Y; + static const Scalar HI_FEAT_SURF_COLOR; + + static const string HI_FEAT_SIFT_LABEL; + static const int HI_FEAT_SIFT_LABEL_X; + static const int HI_FEAT_SIFT_LABEL_Y; + static const int HI_FEAT_SIFT_CLASS_X; + static const int HI_FEAT_SIFT_CLASS_Y; + static const int HI_FEAT_SIFT_CHKBOX_MATCH_X; + static const int HI_FEAT_SIFT_CHKBOX_MATCH_Y; + static const int HI_FEAT_SIFT_CHKBOX_NON_MATCH_X; + static const int HI_FEAT_SIFT_CHKBOX_NON_MATCH_Y; + static const int HI_FEAT_SIFT_COUNT_MATCH_X; + static const int HI_FEAT_SIFT_COUNT_MATCH_Y; + static const int HI_FEAT_SIFT_COUNT_NON_MATCH_X; + static const int HI_FEAT_SIFT_COUNT_NON_MATCH_Y; + static const int HI_FEAT_SIFT_MAX_MATCH_X; + static const int HI_FEAT_SIFT_MAX_MATCH_Y; + static const int HI_FEAT_SIFT_MAX_NON_MATCH_X; + static const int HI_FEAT_SIFT_MAX_NON_MATCH_Y; + static const Scalar HI_FEAT_SIFT_COLOR; + + static const string HI_FEAT_MSER_LABEL; + static const int HI_FEAT_MSER_LABEL_X; + static const int HI_FEAT_MSER_LABEL_Y; + static const int HI_FEAT_MSER_CLASS_X; + static const int HI_FEAT_MSER_CLASS_Y; + static const int HI_FEAT_MSER_CHKBOX_MATCH_X; + static const int HI_FEAT_MSER_CHKBOX_MATCH_Y; + static const int HI_FEAT_MSER_CHKBOX_NON_MATCH_X; + static const int HI_FEAT_MSER_CHKBOX_NON_MATCH_Y; + static const int HI_FEAT_MSER_COUNT_MATCH_X; + static const int HI_FEAT_MSER_COUNT_MATCH_Y; + static const int HI_FEAT_MSER_COUNT_NON_MATCH_X; + static const int HI_FEAT_MSER_COUNT_NON_MATCH_Y; + static const int HI_FEAT_MSER_MAX_MATCH_X; + static const int HI_FEAT_MSER_MAX_MATCH_Y; + static const int HI_FEAT_MSER_MAX_NON_MATCH_X; + static const int HI_FEAT_MSER_MAX_NON_MATCH_Y; + static const Scalar HI_FEAT_MSER_COLOR; + + static const string HI_FEAT_CRYPTS_LABEL; + static const int HI_FEAT_CRYPTS_LABEL_X; + static const int HI_FEAT_CRYPTS_LABEL_Y; + static const int HI_FEAT_CRYPTS_CLASS_X; + static const int HI_FEAT_CRYPTS_CLASS_Y; + static const int HI_FEAT_CRYPTS_CHKBOX_MATCH_X; + static const int HI_FEAT_CRYPTS_CHKBOX_MATCH_Y; + static const int HI_FEAT_CRYPTS_COUNT_MATCH_X; + static const int HI_FEAT_CRYPTS_COUNT_MATCH_Y; + static const int HI_FEAT_CRYPTS_MAX_MATCH_X; + static const int HI_FEAT_CRYPTS_MAX_MATCH_Y; + static const Scalar HI_FEAT_CRYPTS_COLOR; + + static const int HI_UNDO_REMOVAL_BUTTON_X; + static const int HI_UNDO_REMOVAL_BUTTON_Y; + static const int HI_UNDO_REMOVAL_BUTTON_WIDTH; + static const int HI_UNDO_REMOVAL_BUTTON_HEIGHT; + static const string HI_UNDO_REMOVAL_BUTTON_LABEL; + + // score window + static const string SCORE_WINDOW_TITLE; + static const int SCORE_WINDOW_X; + static const int SCORE_WINDOW_Y; + static const int SCORE_WINDOW_WIDTH; + static const int SCORE_WINDOW_HEIGHT; + static const double SCORE_LABEL_SIZE; + static const int SCORE_LABEL_X; + static const int SCORE_LABEL_Y; + static const double SCORE_VALUE_SIZE; + static const int SCORE_VALUE_X; + static const int SCORE_VALUE_Y; + static const unsigned int SCORE_VALUE_COLOR; + static const string SCORE_GENUINE_LABEL; + static const unsigned int SCORE_GENUINE_COLOR; + static const string SCORE_IMPOSTOR_LABEL; + static const unsigned int SCORE_IMPOSTOR_COLOR; + + // remove-annotation popup window + static const string ANNOT_REMOVAL_POPUP_TITLE; + static const int ANNOT_REMOVAL_POPUP_WIDTH; + static const int ANNOT_REMOVAL_POPUP_HEIGHT; + static const string ANNOT_REMOVAL_POPUP_REMOVE_OP; + static const string ANNOT_REMOVAL_POPUP_CANCEL_OP; + + // remove-keypoint popup window + static const string KP_REMOVAL_POPUP_TITLE; + static const int KP_REMOVAL_POPUP_WIDTH; + static const int KP_REMOVAL_POPUP_HEIGHT; + static const string KP_REMOVAL_POPUP_REMOVE_OP; + static const string KP_REMOVAL_POPUP_CANCEL_OP; + + // report content + static const string REPORT_TITLE; + + static const string REPORT_HEADER_1; + static const string REPORT_TABLE_1_HEADER_1; + static const string REPORT_TABLE_1_HEADER_2; + + static const string REPORT_HEADER_2; + static const string REPORT_TABLE_2_HEADER_1; + static const string REPORT_TABLE_2_HEADER_2; + + static const string REPORT_HEADER_3; + static const string REPORT_TABLE_3_HEADER_1; + static const string REPORT_TABLE_3_HEADER_2; + + static const string REPORT_HEADER_4; + static const string REPORT_TABLE_4_HEADER_1; + static const string REPORT_TABLE_4_HEADER_2; + + static const string REPORT_HEADER_5; + static const string REPORT_TABLE_5_HEADER_1; + static const string REPORT_TABLE_5_HEADER_2; + + static const string REPORT_HEADER_6; + static const string REPORT_TABLE_6_HEADER_1; + static const string REPORT_TABLE_6_HEADER_2; + + static const string REPORT_HEADER_7; + static const string REPORT_TABLE_7_HEADER_1; + + static const string REPORT_HEADER_8; + static const string REPORT_TABLE_8_HEADER_1; + static const string REPORT_TABLE_8_HEADER_2; + static const string REPORT_TABLE_8_HEADER_3; + static const string REPORT_TABLE_8_HEADER_4; + static const string REPORT_TABLE_8_COLUMN_1_ROW_01; + static const string REPORT_TABLE_8_COLUMN_1_ROW_02; + static const string REPORT_TABLE_8_COLUMN_1_ROW_03; + static const string REPORT_TABLE_8_COLUMN_1_ROW_04; + static const string REPORT_TABLE_8_COLUMN_1_ROW_05; + static const string REPORT_TABLE_8_COLUMN_1_ROW_06; + static const string REPORT_TABLE_8_COLUMN_1_ROW_07; + static const string REPORT_TABLE_8_COLUMN_1_ROW_08; + static const string REPORT_TABLE_8_COLUMN_1_ROW_09; + static const string REPORT_TABLE_8_COLUMN_1_ROW_10; + static const string REPORT_TABLE_8_COLUMN_1_ROW_11; + static const string REPORT_TABLE_8_COLUMN_1_ROW_12; + + static const string REPORT_GENUINE_LABEL; + static const string REPORT_IMPOSTOR_LABEL; + + // manual iris segmentation window + static const string SEGMENT_WINDOW_TITLE; + static const int SEGMENT_WINDOW_BOTTOM_HEIGHT; + static const int SEGMENT_WINDOW_HORIZ_PADDING; + static const int SEGMENT_WINDOW_VERTC_PADDING; + static const int SEGMENT_IRIS_HEIGHT; + static const string SEGMENT_MESSAGE_1; + static const string SEGMENT_MESSAGE_2; + static const string SEGMENT_MESSAGE_3; + static const string SEGMENT_BUTTON_LABEL_1; + static const string SEGMENT_BUTTON_LABEL_2; + static const string SEGMENT_BUTTON_LABEL_3; + static const string SEGMENT_BUTTON_LABEL_4; + static const int SEGMENT_POINT_SIZE; + static const int SEGMENT_LINE_WIDTH; + static const int SEGMENT_CTRL_PNT_DIST_TOLERANCE; + static const Scalar SEGMENT_PUPIL_COLOR; + static const Scalar SEGMENT_IRIS_COLOR; + static const Scalar SEGMENT_REGION_COLOR; + static const Scalar SEGMENT_CLOSED_REGION_COLOR; +}; + +#endif \ No newline at end of file diff --git a/tshepii/src/CryptDetector.cpp b/tshepii/src/CryptDetector.cpp new file mode 100755 index 0000000..772ceb9 --- /dev/null +++ b/tshepii/src/CryptDetector.cpp @@ -0,0 +1,649 @@ +// ####################################################################### +// NOTICE +// +// This software (or technical data) was produced for the U. S. Government +// and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +// (May 2014) – Alternative IV (Dec 2007) +// +// (c) 2024 The MITRE Corporation. All Rights Reserved. +// ####################################################################### + +/** @file Implementation of the CryptDetector class. */ + +#include "CryptDetector.hpp" +#include "Configuration.hpp" +#include "Iris.hpp" +#include "Functions.hpp" + +using org::mitre::iwp::buffers::TShepiiResponse; +using std::set; + +/* MATLAB inspired functions. */ +/** Implements the MATLAB "imreconstruct" function. */ +void imreconstruct(Mat *marker, Mat *mask, Mat *output) +{ + Mat dilateKernel = getStructuringElement(MORPH_RECT, Size(3, 3)); + + Mat m1 = marker->clone(); + while (true) + { + Mat m2; + dilate(m1, m2, dilateKernel); + min(m2, *mask, m2); + + if (countNonZero(m1 != m2) > 0) + m1 = m2.clone(); + else + { + m2.copyTo(*output); + break; + } + } +} + +/** Implements the MATLAB "imfill" function, for the particular case of masks. */ +void maskfill(Mat *mask, Mat *output) +{ + Mat floodedImg = mask->clone(); + threshold(floodedImg, floodedImg, 0, 255, THRESH_BINARY_INV); + floodFill(floodedImg, Point(0, 0), Scalar(0)); + + bitwise_or(floodedImg, *mask, *output); +} + +/** Implements the MATLAB "bwconncomp" function. */ +void bwconncomp(Mat *inputImage, double minArea, vector> *components) +{ + Mat *canny = new Mat(); + Canny(*inputImage, *canny, 1, 1); + morphologyEx(*canny, *canny, MORPH_CLOSE, getStructuringElement(MORPH_RECT, Size(3, 3))); + + vector> regions; + vector *regionHierarchy = new vector(); + findContours(*canny, regions, *regionHierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE); + delete regionHierarchy; + delete canny; + + for (int i = 0; i < regions.size(); i++) + { + double area = contourArea(regions.at(i)); + if (area >= minArea) + components->push_back(regions.at(i)); + } +} +/** Implements the MATLAB "bwareaopen" function. */ +void bwareaopen(Mat *inputImage, double minArea, Mat *output) +{ + vector> regions; + bwconncomp(inputImage, minArea, ®ions); + + *output = Mat::zeros(inputImage->size(), CV_8UC1); + for (int i = 0; i < regions.size(); i++) + drawContours(*output, regions, i, Scalar(255, 255, 255), -1); +} + +/** Restores the occlusion mask. + * Based on Dr. Feng Shen's MATLAB implementation. */ +void restoreOcclusionMask(Mat *inputImage, Mat *occlusionMask, Mat *output) +{ + Mat inputMaskComplement = Scalar::all(255) - *occlusionMask; + + *output = Mat::zeros(inputImage->size(), inputImage->type()); + inputImage->copyTo(*output, inputMaskComplement); +} + +/** Implements the proper morpohological operation. + * Based on Mr. Jianxu Chen's MATLAB implementation. */ +void morphOperator(Mat *inputImage, Mat *structuringElement, Mat *occlusionMask, Mat *output) +{ + // image dilate and complement + Mat *aux1 = new Mat(); + dilate(*inputImage, *aux1, *structuringElement); + + Mat *aux2 = new Mat(); + restoreOcclusionMask(aux1, occlusionMask, aux2); + delete aux1; + + // reconstruct + Mat *markerImage = new Mat(Scalar::all(255) - *aux2); + Mat *maskImage = new Mat(Scalar::all(255) - *inputImage); + delete aux2; + + Mat *rctR = new Mat(); + imreconstruct(markerImage, maskImage, rctR); + delete markerImage; + delete maskImage; + + Mat *rctRComp = new Mat(Scalar::all(255) - *rctR); + delete rctR; + + Mat *mm = new Mat(*rctRComp - *inputImage); + delete rctRComp; + + Mat r; + threshold(*mm, r, 0, 255, THRESH_BINARY); + r.convertTo(r, CV_8UC1); + delete mm; + + // remove masked areas + restoreOcclusionMask(&r, occlusionMask, output); +} + +/** Implements the hierarchical detection process of iris crypts. + * Returns a mask containing the crypts. + * Based on Mr. Jianxu Chen's MATLAB implementation. */ +void hierarchicalSegmentation(Mat *backgroundSubtracted, Mat *inputImage, Mat *occlusionMask, Mat *prevLevelMask, int strElSize, + double minArea, double maxAreaLowerBound, double maxAreaUpperBound, double cutStd, + Mat *output) +{ + Mat om = occlusionMask->clone(); + Mat raw = inputImage->clone(); + + if (raw.channels() == 3) + { + om = Mat::zeros(raw.size(), CV_8UC1); + cvtColor(raw, raw, CV_BGR2GRAY); + } + + /*********************************/ + /* segmentation at current level */ + /*********************************/ + // morphological reconstruction using the scale of current level + Mat *li = new Mat(); + Mat *strEl = new Mat(getStructuringElement(MORPH_ELLIPSE, Size(strElSize, strElSize))); + morphOperator(&raw, strEl, &om, li); + delete strEl; + + // apply the mask of previous level + Mat *maskedLi = new Mat(); + li->copyTo(*maskedLi, *prevLevelMask); + delete li; + + // clear boundary + maskedLi->row(0) = Mat::zeros(1, maskedLi->cols, maskedLi->type()); + maskedLi->row(maskedLi->rows - 1) = Mat::zeros(1, maskedLi->cols, maskedLi->type()); + maskedLi->col(0) = Mat::zeros(maskedLi->rows, 1, maskedLi->type()); + maskedLi->col(maskedLi->cols - 1) = Mat::zeros(maskedLi->rows, 1, maskedLi->type()); + + // break weak connection + Mat *r = new Mat(); + morphologyEx(*maskedLi, *r, MORPH_OPEN, getStructuringElement(MORPH_ELLIPSE, Size(3, 3))); + delete maskedLi; + + // remove small regions + Mat *fea = new Mat(); + bwareaopen(r, minArea, fea); + delete r; + + // fill holes + Mat filledFea; + maskfill(fea, &filledFea); + delete fea; + + /*********************/ + /* iterative process */ + /*********************/ + Mat iterRegion = Mat::zeros(raw.size(), CV_8UC1); + bool treatNextLevel = false; + + vector> *components = new vector>(); + bwconncomp(&filledFea, minArea, components); + + for (int i = 0; i < components->size(); i++) + { + double area = contourArea(components->at(i)); + if (area > maxAreaLowerBound) + { + if (area > maxAreaUpperBound) + { + drawContours(filledFea, *components, i, Scalar(0, 0, 0), -1); + drawContours(iterRegion, *components, i, Scalar(255, 255, 255), -1); + treatNextLevel = true; + } + + else + { + Mat *auxMask = new Mat(Mat::zeros(backgroundSubtracted->size(), CV_8UC1)); + fillConvexPoly(*auxMask, components->at(i), Scalar(255, 255, 255)); + + Mat *maskedBackgroundSubtracted = new Mat(); + backgroundSubtracted->copyTo(*maskedBackgroundSubtracted, *auxMask); + delete auxMask; + + Scalar mean, stdev; + meanStdDev(*maskedBackgroundSubtracted, mean, stdev); + + delete maskedBackgroundSubtracted; + + if (stdev[0] > cutStd) + { + drawContours(filledFea, *components, i, Scalar(0, 0, 0), -1); + drawContours(iterRegion, *components, i, Scalar(255, 255, 255), -1); + + treatNextLevel = true; + } + } + } + } + delete components; + + // need to perform segmentation on the next level + if (treatNextLevel) + { + if (strElSize >= Configuration::CRYPTS_MIN_ITERATIVE_STR_EL_SIZE) + { + Mat nextLevelMask; + hierarchicalSegmentation(backgroundSubtracted, &raw, &om, &iterRegion, + strElSize - 2, minArea, maxAreaLowerBound, maxAreaUpperBound, cutStd, &nextLevelMask); + + Mat result = Mat::zeros(filledFea.size(), CV_16UC1); + result = filledFea + nextLevelMask; + + threshold(result, *output, 0, 255, CV_8UC1); + } + else + { + Mat result = Mat::zeros(filledFea.size(), CV_16UC1); + result = filledFea + iterRegion; + + threshold(result, *output, 0, 255, CV_8UC1); + } + } + + else + threshold(filledFea, *output, 0, 255, CV_8UC1); +} + +/* Actual CryptDetector class implementation. */ +CryptDetector::CryptDetector(Mat *inputIris, Mat *inputIrisMask, Mat *outputIris, Mat *originalIris, + double irisRadius, double irisX, double irisY, + double pupilRadius, double pupilX, double pupilY, + Scalar color) +{ + this->inputIris = inputIris; + this->inputIrisMask = inputIrisMask; + this->outputIris = outputIris; + this->originalIris = originalIris; + + this->irisRadius = irisRadius; + this->pupilRadius = pupilRadius; + this->irisX = irisX; + this->irisY = irisY; + this->pupilX = pupilX; + this->pupilY = pupilY; + this->color = color; + + this->keypointRenderCount = 0; + this->keypointToMissingRenderCount = 0; + this->missingKeypointRenderCount = 0; + + this->selectedKeypointId = -1; + this->selectedKeypointGrp = -1; +} + +void CryptDetector::getKeypointIdAt(int x, int y, int *id, int *grp, double *distance) +{ + *id = -1; + *grp = -1; + *distance = -1.0; + + double minDistance = DBL_MAX; + + int kpCount = 0; + for (int i = 0; i < this->matchedKeypointIndices.size() && kpCount < this->keypointRenderCount; i++) + if (this->removedMatchedKeypointIndices.find(i) == this->removedMatchedKeypointIndices.end()) + { + kpCount++; + + KeyPoint currentKp = this->sortedKeypoints.at(this->matchedKeypointIndices.at(i)); + double currentDistance = sqrt(pow(currentKp.pt.x - x, 2) + pow(currentKp.pt.y - y, 2)); + + if (currentDistance <= currentKp.size / 2.0 && currentDistance < minDistance) + { + *id = i; + *grp = 0; + *distance = currentDistance; + minDistance = currentDistance; + } + } + + for (int i = 0; i < this->matchedToMissingKeypointIndices.size() && i < this->keypointToMissingRenderCount; i++) + if (this->removedMatchedToMissingKeypointIndices.find(i) == this->removedMatchedToMissingKeypointIndices.end()) + { + KeyPoint currentKp = this->sortedKeypoints.at(this->matchedToMissingKeypointIndices.at(i)); + double currentDistance = sqrt(pow(currentKp.pt.x - x, 2) + pow(currentKp.pt.y - y, 2)); + + if (currentDistance <= currentKp.size / 2.0 && currentDistance < minDistance) + { + *id = i; + *grp = 1; + *distance = currentDistance; + minDistance = currentDistance; + } + } + + for (int i = 0; i < this->missingKeypointIndices.size() && i < this->missingKeypointRenderCount; i++) + if (this->removedMissingKeypointIndices.find(i) == this->removedMissingKeypointIndices.end()) + { + KeyPoint currentKp = this->missingKeypoints.at(this->missingKeypointIndices.at(i)); + double currentDistance = sqrt(pow(currentKp.pt.x - x, 2) + pow(currentKp.pt.y - y, 2)); + + if (currentDistance <= currentKp.size / 2.0 && currentDistance < minDistance) + { + *id = i; + *grp = 2; + *distance = currentDistance; + minDistance = currentDistance; + } + } +} + +void CryptDetector::selectKeypoint(int id, int grp) +{ + this->selectedKeypointId = id; + this->selectedKeypointGrp = grp; +} + +void CryptDetector::resetSelection() +{ + this->selectedKeypointId = -1; + this->selectedKeypointGrp = -1; +} + +void CryptDetector::removeKeypoint(int id, int grp) +{ + if (grp == 0) + { + this->removedMatchedKeypointIndices.insert(id); + + array removed = {id, grp}; + this->removalHistory.push_back(removed); + } + + else if (grp == 1) + { + this->removedMatchedToMissingKeypointIndices.insert(id); + + array removed = {id, grp}; + this->removalHistory.push_back(removed); + } + + else if (grp == 2) + { + this->removedMissingKeypointIndices.insert(id); + + array removed = {id, grp}; + this->removalHistory.push_back(removed); + } +} + +void CryptDetector::undoLastRemoval() +{ + if (!this->removalHistory.empty()) + { + array lastRemoved = this->removalHistory.back(); + this->removalHistory.pop_back(); + + if (lastRemoved[1] == 0) + this->removedMatchedKeypointIndices.erase(lastRemoved[0]); + else if (lastRemoved[1] == 1) + this->removedMatchedToMissingKeypointIndices.erase(lastRemoved[0]); + else if (lastRemoved[1] == 2) + this->removedMissingKeypointIndices.erase(lastRemoved[0]); + } +} + +void CryptDetector::getPolarCoordinates(double x, double y, double *r, double *a) +{ + // Consider the pupil center as the pole + x = x - this->pupilX; + y = y - this->pupilY; + + double angle = atan2(y, x); + if (angle < 0.0) + angle = angle + 6.2831853; // two pi + + double irisX = this->irisX - this->pupilX; + double irisY = this->irisY - this->pupilY; + + double irisPosition = this->irisRadius - sqrt(pow(x - irisX, 2) + pow(y - irisY, 2)); + double pupilPosition = sqrt(pow(x, 2) + pow(y, 2)) - this->pupilRadius; + double rPosition = pupilPosition / (pupilPosition + irisPosition); + + *r = rPosition; + *a = angle; +} + +void CryptDetector::getCarthesianCoordinates(double r, double a, double *x, double *y) +{ + double hyp = (this->irisRadius - this->pupilRadius) * r + this->pupilRadius; + + *x = cos(a) * hyp + this->pupilX; + *y = sin(a) * hyp + this->pupilY; +} + +void CryptDetector::addMatchedKeypointIndex(int index) +{ + this->matchedKeypointIndices.push_back(index); +} + +void CryptDetector::addMatchedToMissingKeypointIndex(int index) +{ + this->matchedToMissingKeypointIndices.push_back(index); +} + +void CryptDetector::addMissingKeypointIndex(int index) +{ + this->missingKeypointIndices.push_back(index); +} + +void CryptDetector::setKeypointRenderCount(int keypointRenderCount) +{ + this->keypointRenderCount = keypointRenderCount; +} + +void CryptDetector::setKeypointToMissingRenderCount(int keypointToMissingRenderCount) +{ + this->keypointToMissingRenderCount = keypointToMissingRenderCount; +} + +void CryptDetector::setMissingKeypointRenderCount(int missingKeypointRenderCount) +{ + this->missingKeypointRenderCount = missingKeypointRenderCount; +} + +vector *CryptDetector::getSortedKeypoints() +{ + return &(this->sortedKeypoints); +} + +vector *CryptDetector::getMissingKeypoints() +{ + return &(this->missingKeypoints); +} + +Scalar CryptDetector::getColor() +{ + return this->color; +} + +set CryptDetector::getRemovedMatchedKeypointIndices() +{ + return this->removedMatchedKeypointIndices; +} + +set CryptDetector::getRemovedMatchedToMissingKeypointIndices() +{ + return this->removedMatchedToMissingKeypointIndices; +} + +set CryptDetector::getRemovedMissingKeypointIndices() +{ + return this->removedMissingKeypointIndices; +} + +void CryptDetector::getConnectablePoints(list *points, bool missingKeypointFirst) +{ + int kpCount = 0; + + kpCount = this->keypointRenderCount; + for (int i = 0; i < this->matchedKeypointIndices.size() && kpCount > 0; i++) + if (this->removedMatchedKeypointIndices.find(i) == this->removedMatchedKeypointIndices.end()) + { + points->push_back(this->sortedKeypoints.at(this->matchedKeypointIndices.at(i)).pt); + kpCount--; + } + + if (!missingKeypointFirst) + { + kpCount = this->keypointToMissingRenderCount; + for (int i = 0; i < this->matchedToMissingKeypointIndices.size() && kpCount > 0; i++) + if (this->removedMatchedToMissingKeypointIndices.find(i) == this->removedMatchedToMissingKeypointIndices.end()) + { + points->push_back(this->sortedKeypoints.at(this->matchedToMissingKeypointIndices.at(i)).pt); + kpCount--; + } + + kpCount = this->missingKeypointRenderCount; + for (int i = 0; i < this->missingKeypointIndices.size() && kpCount > 0; i++) + if (this->removedMissingKeypointIndices.find(i) == this->removedMissingKeypointIndices.end()) + { + points->push_back(this->missingKeypoints.at(this->missingKeypointIndices.at(i)).pt); + kpCount--; + } + } + + else + { + kpCount = this->missingKeypointRenderCount; + for (int i = 0; i < this->missingKeypointIndices.size() && kpCount > 0; i++) + if (this->removedMissingKeypointIndices.find(i) == this->removedMissingKeypointIndices.end()) + { + points->push_back(this->missingKeypoints.at(this->missingKeypointIndices.at(i)).pt); + kpCount--; + } + + kpCount = this->keypointToMissingRenderCount; + for (int i = 0; i < this->matchedToMissingKeypointIndices.size() && kpCount > 0; i++) + if (this->removedMatchedToMissingKeypointIndices.find(i) == this->removedMatchedToMissingKeypointIndices.end()) + { + points->push_back(this->sortedKeypoints.at(this->matchedToMissingKeypointIndices.at(i)).pt); + kpCount--; + } + } +} + +int CryptDetector::getOutputArea() +{ + return this->inputIris->rows * this->inputIris->cols; +} + +void CryptDetector::selectAndSortKeypoints(vector *sourceKeypoints, vector *selectedKeypoints) +{ + sort(sourceKeypoints->begin(), sourceKeypoints->end(), Functions::compareKeypoints); + + int i; + for (i = 0; i < sourceKeypoints->size(); i++) + if (sourceKeypoints->at(i).size > Configuration::KP_MIN_SIZE && sourceKeypoints->at(i).size < Configuration::KP_MAX_SIZE) + { + selectedKeypoints->push_back(sourceKeypoints->at(i)); + break; + } + + if (!selectedKeypoints->empty()) + { + Mat *positions = new Mat(Mat::zeros(1, 2, CV_32FC1)); + positions->at(0, 0) = selectedKeypoints->front().pt.x; + positions->at(0, 1) = selectedKeypoints->front().pt.y; + + BFMatcher *matcher = new BFMatcher(NORM_L2); + + for (int j = i; j < sourceKeypoints->size(); j++) + if (sourceKeypoints->at(j).size > Configuration::KP_MIN_SIZE && sourceKeypoints->at(j).size < Configuration::KP_MAX_SIZE) + { + Mat currentPosition = Mat::zeros(1, 2, CV_32FC1); + currentPosition.at(0, 0) = sourceKeypoints->at(j).pt.x; + currentPosition.at(0, 1) = sourceKeypoints->at(j).pt.y; + + vector match; + matcher->match(currentPosition, *positions, match); + + double rate = match.front().distance / ((sourceKeypoints->at(i).size + selectedKeypoints->at(match.front().trainIdx).size) / 2.0); + if (rate > Configuration::KP_COINCIDENCE_TOLERANCE) + { + selectedKeypoints->push_back(sourceKeypoints->at(j)); + positions->push_back(currentPosition); + } + } + + delete matcher; + delete positions; + } +} + +void CryptDetector::detectCrypts() +{ + // holds the detected crypts + vector> *detectedCrypts = new vector>(); + + // background removal + Mat *backgroundSubtracted = new Mat(); + this->originalIris->copyTo(*backgroundSubtracted, *this->inputIrisMask); + + // smooth + Mat *iris = new Mat(Mat::zeros(this->originalIris->size(), CV_64FC3)); + GaussianBlur(*backgroundSubtracted, *iris, Size(Configuration::CRYPTS_SMOOTH_GAUSS_SIZE, Configuration::CRYPTS_SMOOTH_GAUSS_SIZE), Configuration::CRYPTS_SMOOTH_GAUSS_SIGMA); + normalize(*iris, *iris, 0.0, 255.0, CV_MINMAX); + iris->convertTo(*iris, CV_8UC3); + + cvtColor(*backgroundSubtracted, *backgroundSubtracted, CV_BGR2GRAY); + + // hierarchical segmentation + Mat *cryptMask = new Mat(); + Mat *occlusionMask = new Mat(this->inputIrisMask->clone()); + hierarchicalSegmentation(backgroundSubtracted, iris, occlusionMask, this->inputIrisMask, + Configuration::CRYPTS_INITIAL_ITERATIVE_STR_EL_SIZE, + Configuration::CRYPTS_MIN_CRYPT_AREA, + Configuration::CRYPTS_MAX_CRYP_AREA_LB, + Configuration::CRYPTS_MIN_CRYP_AREA_UB, + Configuration::CRYPTS_CUT_STD, + cryptMask); + delete backgroundSubtracted; + delete iris; + delete occlusionMask; + + Mat output; + cryptMask->copyTo(output, *this->inputIrisMask); + delete cryptMask; + + // break weak connections + morphologyEx(output, output, MORPH_OPEN, getStructuringElement(MORPH_ELLIPSE, Size(3, 3))); + + // reject small regions + bwconncomp(&output, Configuration::CRYPTS_MIN_CRYPT_AREA, detectedCrypts); + + // converts the obtained crypts to keypoints + vector *regionKeypoints = new vector(); + vector *sortedRegionKeypoints = new vector(); + for (vector>::iterator it = detectedCrypts->begin(); it != detectedCrypts->end(); ++it) + { + Point2f kpCenter; + float kpRadius; + minEnclosingCircle(*it, kpCenter, kpRadius); + regionKeypoints->push_back(KeyPoint(kpCenter, 2 * kpRadius, 0.0, kpRadius)); + sortedRegionKeypoints->push_back(KeyPoint(kpCenter, 2 * kpRadius, 0.0, float(contourArea(*it)))); + } + this->selectAndSortKeypoints(sortedRegionKeypoints, &(this->sortedKeypoints)); + delete sortedRegionKeypoints; + + for (vector::iterator it1 = this->sortedKeypoints.begin(); it1 != this->sortedKeypoints.end(); ++it1) + for (int i = 0; i < regionKeypoints->size(); i++) + if (it1->pt == regionKeypoints->at(i).pt) + { + this->crypts.push_back(detectedCrypts->at(i)); + break; + } + + delete regionKeypoints; + delete detectedCrypts; +} diff --git a/tshepii/src/CryptDetector.hpp b/tshepii/src/CryptDetector.hpp new file mode 100755 index 0000000..d20d6ed --- /dev/null +++ b/tshepii/src/CryptDetector.hpp @@ -0,0 +1,303 @@ +/************************************************************************* +* TSHEPII: Tool for Supporting Human Examination of Postmortem Iris Images +* Version : 1.0 +* Date : April 2018 +* Authors : Daniel Moreira, Adam Czajka, University of Notre Dame, USA +*************************************************************************/ + +#ifndef CRYPT_DETECTOR_H +#define CRYPT_DETECTOR_H + +#include "Tshepii.pb.h" +#include +#include +#include +#include + +using std::array; +using std::set; +using std::runtime_error; +using std::list; +using std::vector; +using cv::Scalar; +using cv::KeyPoint; +using cv::Point; +using cv::Mat; +using org::mitre::iwp::buffers::TShepiiResponse; + +/** Implements a detector of crypts over iris images. */ +class CryptDetector { +public: + /** Constructor. + * + * @param [in] inputIris The iris to be processed. + * @param [in] inputirisMask A mask to be taken into account when processing the given iris. + * @param [in] outputIris The result of processing the given iris. + * @param [in] originalIris The original version of the input iris image, without any previous processing. + * + * @param [in] irisRadius The radius, in pixels, of the iris depicted in the input image. + * @param [in] irisX The x coordinate of the iris depicted in the input image. + * @param [in] irisY The y coordinate of the iris depicted in the input image. + * + * @param [in] pupilRadius The radius, in pixels, of the pupil of the iris depicted in the input image. + * @param [in] pupilX The x coordinate of the pupil. + * @param [in] pupilY The y coordinate of the pupil. + * + * @param [in] color The color for rendering the detected interest points. + */ + CryptDetector(Mat *inputIris, Mat *inputIrisMask, Mat *outputIris, Mat *originalIris, + double irisRadius, double irisX, double irisY, + double pupilRadius, double pupilX, double pupilY, + Scalar color); + + /** Constructor of iris processors. + * @param [in] inputIris The iris to be processed. + * @param [in] inputIrisMask A mask to be taken into account when processing the given iris. + * @param [in] outputIris The result of processing the given iris. + * @param [in] originalIris The original version of the input iris image, without any previous processing. + */ + CryptDetector(Mat *inputIris, Mat *inputIrisMask, Mat *outputIris, Mat *originalIris) { + this->inputIris = inputIris; + this->inputIrisMask = inputIrisMask; + this->outputIris = outputIris; + this->originalIris = originalIris; + } + + /** Destructor. */ + ~CryptDetector() { + this->inputIris = NULL; + this->inputIrisMask = NULL; + this->outputIris = NULL; + this->originalIris = NULL; + } + + /** Returns a pointer to the input iris mask. + * @return The input iris mask. */ + Mat* getInputIrisMask() { + return this->inputIrisMask; + } + /** Obtains the id and group of the interest point that occupies the given (, ) iris image position. + * @param [in] x The x coordinate of the given target position. + * @param [in] y The y coordinate of the given taget position. + * @param [out] id The id (i.e., the position on the proper list) of the obtained interest point. + * It will be equal to -1 if no point occupies the given position. + * @param [out] grp The group of the obtained interest point (0: for truly matched, 1: for matched to missing, and 2: for stub interest point). + * It will be equal to -1 if not applicable. + * @param [out] distance The distance between the interest pointer center and the target position. + * It will be equal to -1 if not applicable. + * @return void */ + void getKeypointIdAt(int x, int y, int *id, int *grp, double *distance); + + /** Marks the given interest point as currently selected. + * @param [in] id The id (position in the proper list) of the selected interest point. + * @param [in] grp The group of the selected interest point (0: for truly matched, 1: for matched to missing, and 2: for stub interest point). + * @return void */ + void selectKeypoint(int id, int grp); + + /** Marks no interest point as currently selected. + * @return void */ + void resetSelection(); + + /** Removes the given interest point from the set of detected points. + * @param [in] id The id (position in the proper list) of the selected interest point. + * @param [in] grp The group of the selected interest point (0: for truly matched, 1: for matched to missing, and 2: for stub interest point). + * @return void */ + void removeKeypoint(int id, int grp); + + /** Cancels the last interest point removal in the removal history. + * @return void */ + void undoLastRemoval(); + + /** Computes the polar coordinates of the given position, concerning the given input iris. + * @param [in] x The x coordinate of the position to be transformed. + * @param [in] y The y coordinate of the position to be transformed. + * @param [out] r The radial position of the obtained polar coordinate. + * @param [out] a The angle of the obtained polar coordinate. + * @return void */ + void getPolarCoordinates(double x, double y, double *r, double *a); + + /** Computes the carthesian coordinates of the given polar position, concerning the given input iris. + * @param [in] r The radial position of the polar coordinate to be transformed. + * @param [in] a The angle of the polar coordinate to be transformed. + * @param [out] x The x coordinate of the obtained carthesian position. + * @param [out] y The y coordinate of the obtained carthesian position. + * @return void */ + void getCarthesianCoordinates(double r, double a, double *x, double *y); + + /** Adds the given index to the list of positions of matched interest points. + * @param [in] index The index of the truly matched interest point, within the list of sorted interest points. + * @return void */ + void addMatchedKeypointIndex(int index); + + /** Adds the given index to the list of positions of interest points that were matched to missing interest points in the other iris image. + * @param [in] index The index of the interest point matched to a missing interest point on the other iris image. + * @return void */ + void addMatchedToMissingKeypointIndex(int index); + + /** Adds the given index to the list of positions of the missing (stub) interest points that were matched to interest points in the other iris image. + * @param [in] index The index of the missing interest point matched to an interest point on the other iris image. + * @return void */ + void addMissingKeypointIndex(int index); + + /** Configures the number of matched interest points that must be rendered. + * @param [in] keypointRenderCount The number of interest points. + * @return void */ + void setKeypointRenderCount(int keypointRenderCount); + + /** Configures the number of interest points matched to missing interest points on the other iris image that must be rendered. + * @param [in] keypointToMissingRenderCount The number of interest points. + * @return void */ + void setKeypointToMissingRenderCount(int keypointToMissingRenderCount); + + /** Configures the number of missing (stub) interest points that must be rendered. + * @param [in] missingKeypointRenderCount The number of interest points. + * @retutn void */ + void setMissingKeypointRenderCount(int missingKeypointRenderCount); + + /** Returns a pointer to the obtained and sorted interest points, according to their strengh. + * @return The obtained and sorted interest points, according to their response. */ + vector* getSortedKeypoints(); + + /** Returns a pointer to the list of missing (stub) interest points. + * @return The list of missing (stub) interest points. */ + vector* getMissingKeypoints(); + + /** Returns the color used for rendering the detected interest points. + * @return The color used for rendering the detected interest points. */ + Scalar getColor(); + + /** Returns the positions of the removed truly matched interest points. + * @return The positions of the removed truly matched interest points. */ + set getRemovedMatchedKeypointIndices(); + + /** Returns the positions of the removed interest points that were matched to missing interest points in the other iris image. + * @return The positions of the removed interest points that were matched to missing interest points in the other iris image. */ + set getRemovedMatchedToMissingKeypointIndices(); + + /** Returns the positions of the removed stub (a.k.a. missing) interest points. + * @return The positions of the removed stub (a.k.a. missing) interest points. */ + set getRemovedMissingKeypointIndices(); + + /** Returns the interest point positions that must be visually linked in the application interface. + * @param [out] points It will hold a list with the valid interest point centers. + * @param [out] missingKeypointFirst TRUE if the missing interest points must be listed before the true interest points + * that are matched to missing interest points on the other iris image, FALSE otherwise. + * @return void */ + void getConnectablePoints(list *points, bool missingKeypointFirst); + + /** Return the size, in number of pixels, of the output iris image. + * @return The number of pixels of the output iris image. */ + int getOutputArea(); + + /** Detects and describes interest points over the original iris image. + * Each subclass of CryptDetector MUST implement this method. + * @param [out] descriptions The descriptions of the detected interest points. + * @return void */ + virtual void detectAndDescribeSortedKeypoints(Mat *descriptions) { + throw runtime_error("Don't forget to implement YourClass::detectAndDescribeSortedKeypoints()."); + } + + /** Describes the given interest point concerning the original iris image. + * Each subclass of CryptDetector MUST implement this method. + * @param [in] keypoint The keypoint to be described. + * @param [out] description The described interest point. + * @return void */ + virtual void describeKeypoint(KeyPoint *keypoint, Mat *description) { + throw runtime_error("Don't forget to implement YourClass::describeKeypoint(KeyPoint *keypoint)."); + } + + /** Extracts iris crypts from the input image, according to the work of Chen et. al. + * @return void */ + void detectCrypts(); + + /** Returns a mask to the wanted crypt. + * @param [in] cryptPosition The position of the crypt in the list of detected crypts. + * @return A mask containing the crypt shape. */ + Mat getCryptMask(int cryptPosition); + + /** Holds the obtained crypts. */ + vector> crypts; + +private: + /** Detected and selected interest points, sorted by response. */ + vector sortedKeypoints; + + /** Holds the missing interest points, that should be detected but were not. */ + vector missingKeypoints; + + /** Holds the positions of the truly matched interest points, within the list of sorted interest points. */ + vector matchedKeypointIndices; + + /** Holds the positions of the interest points matched to missing interest points in the other iris image, within the list of sorted interest points. */ + vector matchedToMissingKeypointIndices; + + /** Holds the positions of the missing interest points matched to interest points in the other iris image, within the list of missing interest points. */ + vector missingKeypointIndices; + + /** Holds the positions of the removed truly matched interest points. */ + set removedMatchedKeypointIndices; + + /** Holds the positions of the removed interest points that were matched to missing interest points in the other iris image. */ + set removedMatchedToMissingKeypointIndices; + + /** Holds the positions of the removed stub (a.k.a. missing) interest points. */ + set removedMissingKeypointIndices; + + /** Stack of removed interest points, containing the point position (inside the proper list) + * and the source group (0: for truly matched, 1: for matched to missing, and 2: for stub interest point). */ + list> removalHistory; + + /** Holds the number of truly matched interest points that must be rendered. */ + int keypointRenderCount; + + /** Holds the number of interest points matched to missing interest points on the other iris image that must be rendered. */ + int keypointToMissingRenderCount; + + /** Holds the number of missing interest points that must be rendered. */ + int missingKeypointRenderCount; + + /** Id of the currently selected interest point (actually, its position on the proper list). + * Its value is -1, when no interest point is selected. */ + int selectedKeypointId; + + /** Group of the currently selected interest point. + * It can be either -1: not applicable, 0: truly matched, 1: matched to missing, or 2: stub interest point. */ + int selectedKeypointGrp; + + /** Selects and sorts keypoints from the given ones. + * @param [in] sourceKeypoints The source of keypoints that must be selected and sorted. + * @param [out] selectedKeypoints The resulting selection of sorted keypoints. + * @return void */ + void selectAndSortKeypoints(vector *sourceKeypoints, vector *selectedKeypoints); + + /** Input iris that must be processed. */ + Mat *inputIris; + + /** Mask of the input image. */ + Mat *inputIrisMask; + + /** Output image that will hold the processed input image. */ + Mat *outputIris; + + /** Original version of the input iris. */ + Mat *originalIris; + + /** Radius of the input iris. */ + double irisRadius; + + /** Radius of the pupil of the input iris. */ + double pupilRadius; + + /** Carthesian center of the input iris. */ + double irisX, irisY; + + /** Carthesian center of the pupil of the input iris. */ + double pupilX, pupilY; + + /** Color of the detected interest points. */ + Scalar color; + static CryptDetector fromProtobuf(TShepiiResponse buf); +}; + +#endif + diff --git a/tshepii/src/CryptMatcher.cpp b/tshepii/src/CryptMatcher.cpp new file mode 100755 index 0000000..acaf260 --- /dev/null +++ b/tshepii/src/CryptMatcher.cpp @@ -0,0 +1,290 @@ +// ####################################################################### +// NOTICE +// +// This software (or technical data) was produced for the U. S. Government +// and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +// (May 2014) – Alternative IV (Dec 2007) +// +// (c) 2024 The MITRE Corporation. All Rights Reserved. +// ####################################################################### + +/** @file Implementation of CryptMatcher class. */ + +#include +#include +#include "CryptMatcher.hpp" +#include "Configuration.hpp" +#include "Functions.hpp" + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327950288 /* pi */ +#endif + +using org::mitre::iwp::buffers::Crypt; +using org::mitre::iwp::buffers::TCircle; +using org::mitre::iwp::buffers::TPoint; +using org::mitre::iwp::buffers::TShepiiResponse; + +CryptMatcher::CryptMatcher(TShepiiResponse *iris1, TShepiiResponse *iris2) : CryptMatcher(iris1, iris2, cv::NORM_L1) {} + +CryptMatcher::CryptMatcher(TShepiiResponse *iris1, TShepiiResponse *iris2, int distanceType) +{ + this->iris1 = iris1; + this->iris2 = iris2; + this->distanceType = distanceType; +} + +/** Destructor. */ +CryptMatcher::~CryptMatcher() +{ + this->iris1 = NULL; + this->iris2 = NULL; +} + +size_t CryptMatcher::getMatchCount() +{ + return this->soundMatches.size(); +} + +size_t CryptMatcher::getStubMatchCount() +{ + return this->stubMatches.size(); +} + +double CryptMatcher::getAvgMatchDistance() +{ + if (!this->soundMatches.empty()) + { + double sum = 0.0; + for (const DMatch &it : this->soundMatches) + sum = sum + fabs(it.distance); + + return sum / this->soundMatches.size(); + } + + // default: max distance + else + return 1.0; +} + +double CryptMatcher::getAvgMismatchDistance() +{ + if (!this->stubMatches.empty()) + { + double sum = 0.0; + for (const DMatch &it : this->stubMatches) + sum = sum + fabs(it.distance); + + return sum / this->stubMatches.size(); + } + + // default: max distance + else + return 1.0; +} + +void CryptMatcher::getPolarCoordinates(TCircle iris, TCircle pupil, + double x, double y, double *radius, double *angle) +{ + + // Consider the pupil center as the pole + x = x - pupil.center().x(); + y = y - pupil.center().y(); + + *angle = atan2(y, x); + if ((*angle) < 0.0) + (*angle) += (2 * M_PI); // two pi + + double irisX = iris.center().x() - pupil.center().x(); + double irisY = iris.center().y() - pupil.center().y(); + + double irisPosition = iris.radius() - sqrt(pow(x - irisX, 2) + pow(y - irisY, 2)); + double pupilPosition = sqrt(pow(x, 2) + pow(y, 2)) - pupil.radius(); + + *radius = pupilPosition / (pupilPosition + irisPosition); +} + +bool CryptMatcher::areSoundPolarCoordinates(double r1, double a1, double r2, double a2) +{ + if (fabs(r1 - r2) > Configuration::KP_MATCHER_POLAR_RADIUS_TOLERANCE) + return false; + + if (fabs(a1 - a2) > Configuration::KP_MATCHER_POLAR_ANGLE_TOLERANCE) + return false; + + return true; +} + +Mat CryptMatcher::getCryptMask(TShepiiResponse *iris, int kpIndex, + int imageWidth, int imageHeight) +{ + Mat mask = Mat::zeros(imageWidth, imageHeight, CV_8UC1); + + TPoint tpoint = iris->keypoints(kpIndex); + float radius = tpoint.size() / 2; + int width = int(round(tpoint.size())); + int height = int(round(tpoint.size())); + + int x = int(round(tpoint.x() - radius)); + while (x < 0) + { + x++; + width--; + } + while (x + width > mask.cols) + width--; + + int y = int(round(tpoint.y() - radius)); + while (y < 0) + { + y++; + height--; + } + while (y + height > mask.rows) + height--; + + vector> *cvCrypts = new vector>(); + for (int i = 0; i < iris->crypts_size(); i++) + { + Crypt *tCrypt = iris->mutable_crypts(i); + vector cvCrypt = vector(); + for (int j = 0; j < tCrypt->points_size(); j++) + { + TPoint tpoint = tCrypt->points(j); + cvCrypt.push_back(Point2i(tpoint.x(), tpoint.y())); + } + cvCrypts->push_back(cvCrypt); + } + drawContours(mask, *cvCrypts, kpIndex, Scalar(255, 255, 255), -1); + return mask(Rect(x, y, width, height)); +} + +void CryptMatcher::detectDescribeAndMatch() +{ + + set *usedKps1 = new set(); + set *usedKps2 = new set(); + // Step 1. Detection + // detects crypts over irises 1 and 2 + TShepiiResponse *irisData1 = this->iris1; + TShepiiResponse *irisData2 = this->iris2; + + if (irisData1->keypoints_size() < irisData2->keypoints_size()) + { + irisData1 = this->iris2; + irisData2 = this->iris1; + } + + // takes the centers of the crypts as 2D descriptions + TPoint tpoint; + Mat *desc1 = new Mat(); + for (size_t i = 0; i < irisData1->keypoints_size(); i++) + { + Mat desc = Mat::zeros(Size(2, 1), CV_32FC1); + desc.at(0, 0) = tpoint.x(); + desc.at(0, 1) = tpoint.y(); + + desc1->push_back(desc); + } + + Mat *desc2 = new Mat(); + for (size_t i = 0; i < irisData2->keypoints_size(); i++) + { + Mat desc = Mat::zeros(Size(2, 1), CV_32FC1); + desc.at(0, 0) = tpoint.x(); + desc.at(0, 1) = tpoint.y(); + + desc2->push_back(desc); + } + + // Step 2. Establishing matches + // matches the detected keypoints, based on their positions + BFMatcher *matcher = new BFMatcher(NORM_L2); + vector> *matches = new vector>(); + matcher->knnMatch(*desc1, *desc2, *matches, desc2->rows); + delete matcher; + + int image1Width = irisData1->inputimagewidth(); + int image1Height = irisData1->inputimageheight(); + int image2Width = irisData2->inputimagewidth(); + int image2Height = irisData2->inputimageheight(); + + // for each match + for (vector>::iterator it = matches->begin(); it != matches->end(); ++it) + for (vector::iterator match = it->begin(); match != it->end(); ++match) + { + // if the current keypoints were not used yet + if (usedKps1->find(match->queryIdx) == usedKps1->end() && + usedKps2->find(match->trainIdx) == usedKps2->end()) + { + // keypoints from images 1 and 2 + TPoint tp1 = irisData1->keypoints(match->queryIdx); + TPoint tp2 = irisData2->keypoints(match->trainIdx); + + // polar coordinates of the matched keypoints + double r1, a1, r2, a2; + this->getPolarCoordinates(irisData1->cropiris(), + irisData1->croppupil(), + tp1.x(), tp1.y(), &r1, &a1); + this->getPolarCoordinates(irisData2->cropiris(), + irisData2->croppupil(), + tp2.x(), tp2.y(), &r2, &a2); + + // if the polar coordinates are sound + if (this->areSoundPolarCoordinates(r1, a1, r2, a2)) + { + // obtains masks to crypts 1 and 2, with the same size + Mat mask1 = this->getCryptMask(irisData1, match->queryIdx, + image1Width, image1Height); + Mat mask2 = this->getCryptMask(irisData2, match->trainIdx, + image2Width, image2Height); + resize(mask2, mask2, mask1.size()); + + Mat diffMask = abs(mask1 - mask2) / 255; + int maskSize = diffMask.rows * diffMask.cols; + + double coincidentCryptArea = maskSize - sum(diffMask)[0]; + double coincidenceRatio = coincidentCryptArea / maskSize; + + // if the content is coincident enough + if (coincidenceRatio > Configuration::CRYPTS_MATCH_COINCIDENCE_TOLERANCE) + { + // registers the use of the current keypoints + irisData1->add_matchindex(match->queryIdx); + irisData2->add_matchindex(match->trainIdx); + + // sets the match distance value + double dist = 1.0 - + (Configuration::CRYPTS_MATCH_DIST_ALPHA * + coincidenceRatio + + (1.0 - Configuration::CRYPTS_MATCH_DIST_ALPHA) * + coincidentCryptArea / (image1Width * image1Height)); + match->distance = float(dist); + + // no need for checking the remaining matches + break; + } + // else, next try... + } + // else, next try... + } + // else, next try... + } + + // frees some memory + delete matches; + + // sorts the obtained matches and registers the matched interest points within the iris detectors + sort(this->soundMatches.begin(), this->soundMatches.end(), Functions::compareMatches); + for (vector::iterator match = this->soundMatches.begin(); match != this->soundMatches.end(); ++match) + { + iris1->add_matchindex(match->queryIdx); + iris2->add_matchindex(match->trainIdx); + } + + // frees some memory + delete usedKps1; + delete usedKps2; + delete desc1; + delete desc2; +} diff --git a/tshepii/src/CryptMatcher.hpp b/tshepii/src/CryptMatcher.hpp new file mode 100755 index 0000000..5852d66 --- /dev/null +++ b/tshepii/src/CryptMatcher.hpp @@ -0,0 +1,117 @@ +/************************************************************************* +* TSHEPII: Tool for Supporting Human Examination of Postmortem Iris Images +* Version : 1.0 +* Date : April 2018 +* Authors : Daniel Moreira, Adam Czajka, University of Notre Dame, USA +*************************************************************************/ + +#ifndef CRYPT_MATCHER_H +#define CRYPT_MATCHER_H + +#include "CryptDetector.hpp" +#include "CryptDetector.hpp" +#include "IrisProcessor.hpp" +#include +#include + +using org::mitre::iwp::buffers::TShepiiResponse; +using org::mitre::iwp::buffers::TCircle; + +/** Implements a matcher of crypts. */ +class CryptMatcher { +public: + /** Constructor. + * @param [in] detector1 The crypt detector that works on input iris 1. + * @param [in] detector2 The crypt detector that works on input iris 2. + * @param [in] progress A pointer to the variable that holds the progress of crypt detection and matching. + * @param [in] matchRenderCount A pointer to the variable that holds the amount of crypt matches that must be rendered by the application. + * @param [in] showMatches TRUE if crypt matches must be rendered by the application, FALSE otherwise. + */ + CryptMatcher(TShepiiResponse *iris1, TShepiiResponse *iris2); + + /** Constructor. + * @param [in] detector1 The interest point detector that works on input iris 1. + * @param [in] detector2 The interest point detector that works on input iris 2. + * @param [in] distanceType The type of distance calculated between the descriptions of the two iris images. + * Give cv::NORM_L2 or another cv::one. + * @param [in] progress A pointer to the variable that holds the progress of interest point detection, description, and matching. + * @param [in] matchRenderCount A pointer to the variable that holds the amount of true matches that must be rendered by the application. + * @param [in] stubMatchRenderCount A pointer to the variable that holds the amount of missed matches that must be rendered by the application. + * @param [in] showMatches TRUE if true matches must be rendered by the application, FALSE otherwise. + * @param [in] showStubMatches TRUE if missed matches must be rendered by the application, FALSE otherwise. + */ + CryptMatcher(TShepiiResponse *iris1, TShepiiResponse *iris2, int distanceType); + + /** Destructor. */ + ~CryptMatcher(); + + + Mat getCryptMask(TShepiiResponse *iris, int kpIndex, int imageWidth, + int imageHeight); + + /** Detects, describes, and matches the keypoints belonging to irises 1 and 2. + * It highlights the unmatched interest points by establishing stub matches between them and + * the respective regions on the opposite iris image. + * @return void */ + void detectDescribeAndMatch(); + + /** Returns the number of established interest-point true matches between irises 1 and 2. + * @return The number of established interest-point true matches between irises 1 and 2. */ + size_t getMatchCount(); + + /** Returns the number of established stub-keypoint (un)matches between irises 1 and 2. + * @return The number of established stub-keypoint (un)matches between irises 1 and 2. */ + size_t getStubMatchCount(); + + /** Return the average distance of the true matches. + * @return The average distance of true matches. */ + double getAvgMatchDistance(); + + /** Returns the average distance of the stub matches. + * @return The average distance of stub matches. */ + double getAvgMismatchDistance(); + +private: + /** Points to the variable that holds the progress of interest point detection, description, and matching. + * Max value for each matcher: 8 (eight steps). */ + int *progress; + + /** Holds the geometrically sound keypoint matches. */ + vector soundMatches; + + /** Verifies if the given polar coordinates are geometrically sound. + * @param [in] r1 The radius of the polar coordinate over iris 1. + * @param [in] a1 The angle of the polar coordinate over iris 1. + * @param [in] r2 The radius of the polar coordinate over iris 2. + * @param [in] a2 The angle of the polar coordinate over iris 2. + * @return TRUE if the coordinates match, FALSE otherwise. */ + bool areSoundPolarCoordinates(double r1, double a1, double r2, double a2); + + void getPolarCoordinates(TCircle iris, TCircle pupil, + double x, double y, double *radius, double *angle); + + /** Image processor of iris 1. */ + TShepiiResponse *iris1; + + /** Image processor of iris 2. */ + TShepiiResponse *iris2; + /** Type of distance calculated between two interest points. */ + int distanceType; + + /** Holds the amount of true matches that must be rendered by the application. */ + int *matchRenderCount; + + /** Holds the amount of missed matches that must be rendered by the application. */ + int *stubMatchRenderCount; + + /** Flags if true matches must be rendered by the application. */ + bool *showMatches; + + /** Flags if missed matches must be rendered by the application. */ + bool *showStubMatches; + + /** Holds the stub matches that help to highlight the unmatched interest points. */ + vector stubMatches; +}; + +#endif diff --git a/tshepii/src/Functions.cpp b/tshepii/src/Functions.cpp new file mode 100755 index 0000000..40b3cc3 --- /dev/null +++ b/tshepii/src/Functions.cpp @@ -0,0 +1,72 @@ +// ####################################################################### +// NOTICE +// +// This software (or technical data) was produced for the U. S. Government +// and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +// (May 2014) – Alternative IV (Dec 2007) +// +// (c) 2024 The MITRE Corporation. All Rights Reserved. +// ####################################################################### + +/** @file Implementation of Fucntions class. */ + +#include +#include "Functions.hpp" + +void Functions::split(string *s, string delimiters, vector *tokens) +{ + stringstream ss; + ss << *s; + + while (true) + { + bool processNext = false; + + for (int i = 0; i < delimiters.length(); i++) + { + string item; + if (getline(ss, item, delimiters.at(i))) + { + tokens->push_back(item); + processNext = true; + break; + } + } + + if (!processNext) + break; + } +} + +string Functions::getCurrentDateTime() +{ + time_t now = time(0); + struct tm tstruct; + char buf[80]; + tstruct = *localtime(&now); + strftime(buf, sizeof(buf), "%Y-%m-%d %X", &tstruct); + return buf; +} + +bool Functions::compareMatches(DMatch match1, DMatch match2) +{ + return fabs(match1.distance) < fabs(match2.distance); +} + +bool Functions::compareKeypoints(KeyPoint kp1, KeyPoint kp2) +{ + return kp1.response > kp2.response; +} + +void Functions::computeMeanStd(vector *data, double *mean, double *std) +{ + *mean = 0.0; + for (vector::iterator it = data->begin(); it != data->end(); ++it) + *mean = *mean + *it; + *mean = *mean / data->size(); + + *std = 0.0; + for (vector::iterator it = data->begin(); it != data->end(); ++it) + *std = *std + pow(*it - *mean, 2); + *std = sqrt(*std / data->size()); +} diff --git a/tshepii/src/Functions.hpp b/tshepii/src/Functions.hpp new file mode 100755 index 0000000..d4e74b2 --- /dev/null +++ b/tshepii/src/Functions.hpp @@ -0,0 +1,49 @@ +/************************************************************************* +* TSHEPII: Tool for Supporting Human Examination of Postmortem Iris Images +* Version : 1.0 +* Date : April 2018 +* Authors : Daniel Moreira, Adam Czajka, University of Notre Dame, USA +*************************************************************************/ + +#ifndef FUNCTIONS_H +#define FUNCTIONS_H + +#include + +using namespace std; +using namespace cv; + +/** Implements a facade/singleton containing auxiliary functions to the TSHEPII tool. */ +class Functions { +public: + /** Splits a given string, according to the given delimiter chars. + * @param [in] s The string to be split. + * @param [in] delimiter The delimiter char used as separator of splits. + * @param [out] tokens A list containing the resulting split parts of the string. */ + static void split(string *s, string delimiters, vector *tokens); + + /** Returns current date/time, format is "YYYY-MM-DD HH:mm:ss". + * @return A string with the current date-time. */ + static string getCurrentDateTime(); + + /** Helps to sort lists of keypoint matches. + * @param [in] match1 One of the matches to be compared. + * @param [in] match2 The other one of the matches to be compared. + * @return TRUE if match1 comes first than match2, in an eventual list, FALSE otherwise. */ + static bool compareMatches(DMatch match1, DMatch match2); + + /** Helps to sort lists of keypoints. + * @param [in] kp1 One of the keypoints to be compared. + * @param [in] kp2 The other one of the keypoints to be compared. + * @return TRUE if kp1 comes first than kp2, in an eventual list, FALSE otherwise. */ + static bool compareKeypoints(KeyPoint kp1, KeyPoint kp2); + + /** Computes the mean and standard deviation of the given data. + * @param [in] data The data whose mean and standard deviation are wanted. + * @param [out] mean The computed mean. + * @param [out] std The computed standard deviation. + * @return void */ + static void computeMeanStd(vector *data, double *mean, double *std); +}; + +#endif diff --git a/tshepii/src/Iris.cpp b/tshepii/src/Iris.cpp new file mode 100755 index 0000000..c870e66 --- /dev/null +++ b/tshepii/src/Iris.cpp @@ -0,0 +1,274 @@ +// ####################################################################### +// NOTICE +// +// This software (or technical data) was produced for the U. S. Government +// and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +// (May 2014) – Alternative IV (Dec 2007) +// +// (c) 2024 The MITRE Corporation. All Rights Reserved. +// ####################################################################### + +/** @file Implementation of Iris class. */ + +#include "Iris.hpp" +#include "Configuration.hpp" +#include "Functions.hpp" +#include "IrisSegmenter.h" + +/** + * Creates an iris from an image in common formats (PNG, JPG, etc.) + * + * @param filepath The path to the image file + */ +Iris::Iris(string filePath) +{ + this->filePath = filePath; + + // configures the Iris ID + vector *irisFilePathParts = new vector(); + Functions::split(&filePath, Configuration::FILE_SEPARATOR, irisFilePathParts); + + this->id = irisFilePathParts->back(); + delete irisFilePathParts; + + // reads the iris image file + this->originalContent = imread(filePath, IMREAD_COLOR); +} + +/** + * Creates an iris from an image in PIXMAP format + * + * @param filepath The path to the image file + * @param width The width of the image in pixels + * @param height The height of the image in pixels + */ +Iris::Iris(string filepath, int width, int height) +{ + + int channels; + FILE *fp = fopen(filepath.c_str(), "rb"); + if (!fp) + { + return; + } + // Determine the number of channels + fseek(fp, 0, SEEK_END); + channels = ftell(fp) / height / width; + fseek(fp, 0, SEEK_SET); + if (channels == 3) + { + this->originalContent = Mat::zeros(height, width, CV_8UC3); + fread(this->originalContent.data, 3, width * height, fp); + } + else + { + // read in the grayscale image + Mat imageMat = Mat::zeros(height, width, CV_8UC1); + fread(imageMat.data, 1, width * height, fp); + // convert this image to 3-color + Mat temp[] = {imageMat, imageMat, imageMat}; + cv::merge(temp, 3, this->originalContent); + } + fclose(fp); +} + +void Iris::osirisPreProcess() +{ + Mat grayScaledIris, mask, stripIris, stripIrisMask; + int irisRadius, pupilRadius; + CvPoint irisCenter, pupilCenter; + + cvtColor(this->originalContent, grayScaledIris, CV_BGR2GRAY); + stripIris = Mat(Configuration::OSIRIS_NORM_IMAGE_COLS, Configuration::OSIRIS_NORM_IMAGE_ROWS, IPL_DEPTH_8U, 1); + stripIrisMask = Mat(Configuration::OSIRIS_NORM_IMAGE_COLS, Configuration::OSIRIS_NORM_IMAGE_ROWS, IPL_DEPTH_8U, 1); + + irisSegmentation( + grayScaledIris, + mask, + stripIris, + stripIrisMask, + pupilCenter, + irisCenter, + pupilRadius, + irisRadius, + Configuration::OSIRIS_MIN_IRIS_DIAMETER, + Configuration::OSIRIS_MIN_PUPIL_DIAMETER, + Configuration::OSIRIS_MAX_IRIS_DIAMETER, + Configuration::OSIRIS_MAX_PUPIL_DIAMETER); + + this->preProcess( + &mask, &stripIris, &stripIrisMask, + Point2d(irisCenter.x, irisCenter.y), irisRadius, + Point2d(pupilCenter.x, pupilCenter.y), pupilRadius); +} + +void Iris::manualPreProcess( + Mat *contentMask, Mat *stripContent, Mat *stripContentMask, + Point2d irisCenter, double radius, + Point2d pupilCenter, double pupilRadius) +{ + this->preProcess(contentMask, stripContent, stripContentMask, irisCenter, radius, pupilCenter, pupilRadius); +} + +string Iris::getFilePath() +{ + return this->filePath; +} + +string Iris::getId() +{ + return this->id; +} + +Mat Iris::getOriginalContent() +{ + return this->originalContent; +} + +Mat Iris::getOriginalContentMask() +{ + return this->originalContentMask; +} + +Point2d Iris::getOriginalIrisCenter() +{ + return this->originalIrisCenter; +} + +double Iris::getOriginalIrisRadius() +{ + return this->originalIrisRadius; +} + +Point2d Iris::getOriginalPupilCenter() +{ + return this->originalPupilCenter; +} + +double Iris::getOriginalPupilRadius() +{ + return this->originalPupilRadius; +} + +Mat Iris::getStripContent() +{ + return this->stripContent; +} + +Mat Iris::getStripContentMask() +{ + return this->stripContentMask; +} + +Mat Iris::getColorCrop() +{ + return this->colorCrop; +} + +Mat Iris::getGrayCrop() +{ + return this->grayCrop; +} + +Mat Iris::getGray3CCrop() +{ + return this->gray3CCrop; +} + +Mat Iris::getCropMask() +{ + return this->cropMask; +} + +double Iris::getCropRadius() +{ + return this->cropRadius; +} + +Point2d Iris::getCropCenter() +{ + return this->cropCenter; +} + +double Iris::getCropPupilRadius() +{ + return this->cropPupilRadius; +} + +Point2d Iris::getCropPupilCenter() +{ + return this->cropPupilCenter; +} + +void Iris::preProcess( + Mat *originalMask, Mat *originalStripContent, Mat *originalStripContentMask, + Point2d originalCenter, double originalRadius, + Point2d originalPupilCenter, double originalPupilRadius) +{ + // saves the original data + this->originalContentMask = originalMask->clone(); + this->originalIrisCenter = originalCenter; + this->originalIrisRadius = originalRadius; + this->originalPupilCenter = originalPupilCenter; + this->originalPupilRadius = originalPupilRadius; + + // auxiliary vesions of the iris + Mat colorIris, grayScaledIris, grayScaled3CIris, mask; + + // initiates the color iris, its grayscaled version, and mask + colorIris = this->originalContent.clone(); + cvtColor(colorIris, grayScaledIris, CV_BGR2GRAY); + mask = originalMask->clone(); + + // iris cropping + int x = int(round(originalCenter.x - (originalRadius + Configuration::IRIS_RADIUS_OFFSET))); + int y = int(round(originalCenter.y - (originalRadius + Configuration::IRIS_RADIUS_OFFSET))); + int size = int(round((originalRadius + Configuration::IRIS_RADIUS_OFFSET) * 2.0)); + while (x < 0 || y < 0) + { + x++; + y++; + size--; + } + + double newCenterX = originalCenter.x - x; + double newCenterY = originalCenter.y - y; + double newPupilCenterX = originalPupilCenter.x - x; + double newPupilCenterY = originalPupilCenter.y - y; + + colorIris = colorIris(Rect(x, y, size, size)); + grayScaledIris = grayScaledIris(Rect(x, y, size, size)); + mask = mask(Rect(x, y, size, size)); + + // iris resizing + resize(colorIris, colorIris, Size(Configuration::IRIS_HEIGHT, Configuration::IRIS_WIDTH), 0.0, 0.0, CV_INTER_CUBIC); + resize(grayScaledIris, grayScaledIris, Size(Configuration::IRIS_HEIGHT, Configuration::IRIS_WIDTH), 0.0, 0.0, CV_INTER_CUBIC); + resize(mask, mask, Size(Configuration::IRIS_HEIGHT, Configuration::IRIS_WIDTH), 0.0, 0.0, CV_INTER_CUBIC); + blur(mask, mask, Size(Configuration::IRIS_MASK_BLUR_SIZE, Configuration::IRIS_MASK_BLUR_SIZE)); + threshold(mask, mask, 0, 255, CV_THRESH_BINARY); + + // iris CLAHE normalization + Ptr normCLAHE = createCLAHE(); + normCLAHE->setTilesGridSize(Size(Configuration::IRIS_CLAHE_TILES, Configuration::IRIS_CLAHE_TILES)); + normCLAHE->setClipLimit(Configuration::IRIS_CLAHE_CLIP); + normCLAHE->apply(grayScaledIris, grayScaledIris); + cvtColor(grayScaledIris, grayScaled3CIris, CV_GRAY2BGR); + + // sets iris attributes + this->colorCrop = colorIris.clone(); + this->grayCrop = grayScaledIris.clone(); + this->gray3CCrop = grayScaled3CIris.clone(); + this->cropMask = mask.clone(); + this->stripContent = originalStripContent->clone(); + this->stripContentMask = originalStripContentMask->clone(); + + this->cropRadius = originalRadius * Configuration::IRIS_WIDTH / double(size); + this->cropCenter = Point2d( + newCenterX * Configuration::IRIS_WIDTH / double(size), + newCenterY * Configuration::IRIS_HEIGHT / double(size)); + + this->cropPupilRadius = originalPupilRadius * Configuration::IRIS_WIDTH / double(size); + this->cropPupilCenter = Point2d( + newPupilCenterX * Configuration::IRIS_WIDTH / double(size), + newPupilCenterY * Configuration::IRIS_HEIGHT / double(size)); +} diff --git a/tshepii/src/Iris.hpp b/tshepii/src/Iris.hpp new file mode 100755 index 0000000..d5058f5 --- /dev/null +++ b/tshepii/src/Iris.hpp @@ -0,0 +1,202 @@ +/************************************************************************* +* TSHEPII: Tool for Supporting Human Examination of Postmortem Iris Images +* Version : 1.0 +* Date : April 2018 +* Authors : Daniel Moreira, Adam Czajka, University of Notre Dame, USA +*************************************************************************/ + +#ifndef IRIS_H +#define IRIS_H + +#include + +using namespace std; +using namespace cv; + +/** Implements an iris data structure for the TSHEPII application. */ +class Iris { +public: + /** Constructor. + * It receives the iris image filepath as a parameter and fills the other iris attributes accordingly. + * It DOES NOT pre-processes the iris content. + * @param [in] filePath The filepath of the iris image. */ + Iris(string filePath); + + /** + * Creates an iris from an image in PIXMAP format + * + * @param filepath The path to the image file + * @param width The width of the image in pixels + * @param height The height of the image in pixels + */ + Iris(string filepath, int width, int height); + + /** Pre-processes the iris content with OSIRIS software. + * @return void */ + void osirisPreProcess(); + + /** Pre-processes the iris content based on the given manually generated data. + * + * @param [in] contentMask Manually generated iris mask. + * @param [in] stripContent Manually generated strip-normalized version of the iris. + * @param [in] stripContentMask Manually generated content mask of the strip-normalized version of the iris. + * + * @param [in] irisCenter The position of the the manually defined iris center. + * @param [in] radius Manually defined iris radius. + * + * @param [in] pupilCenter The position of the the manually defined pupil center. + * @param [in] pupilRadius Manually defined pupil radius. + * + * @return void */ + void manualPreProcess( + Mat *contentMask, Mat *stripContent, Mat *stripContentMask, + Point2d irisCenter, double radius, + Point2d pupilCenter, double pupilRadius); + + /** Returns the filepath of the current iris. + * @return The filepath of the current iris. */ + string getFilePath(); + + /** Returns the iris ID string. + * @return The ID of the iris. */ + string getId(); + + /** Returns the original iris image. + * @return The original iris image. */ + Mat getOriginalContent(); + + /** Returns the original iris content mask. + * @return The original iris content mask. */ + Mat getOriginalContentMask(); + + /** Returns the original iris center. + * @return The original iris center. */ + Point2d getOriginalIrisCenter(); + + /** Returns the original iris radius. + * @return The original iris radius, in pixels. */ + double getOriginalIrisRadius(); + + /** Returns the original pupil center. + * @return The original pupil center. */ + Point2d getOriginalPupilCenter(); + + /** Returns the original pupil radius. + * @return The original pupil radius, in pixels. */ + double getOriginalPupilRadius(); + + /** Returns the strip-normalized version of the iris. + * @return The strip-normalized version of the iris. */ + Mat getStripContent(); + + /** Returns the mask of the strip-normalized version of the iris. + * @return The mask of the strip-normalized version of the iris. */ + Mat getStripContentMask(); + + /** Returns the colorful cropped version of the iris. + * @return The colorful cropped version of the iris. */ + Mat getColorCrop(); + + /** Returns the grayscaled cropped version of the iris. + * @return The grayscaled cropped version of the iris.*/ + Mat getGrayCrop(); + + /** Returns the 3-channel grayscaled cropped version of the iris. + * @return The 3-channel grayscaled cropped version of the iris. */ + Mat getGray3CCrop(); + + /** Returns the mask to the cropped version of the iris. + * @return The mask to the cropped version of the iris. */ + Mat getCropMask(); + + /** Returns the radius of the cropped iris. + * @return The radius of the cropped iris, in pixels. */ + double getCropRadius(); + + /** Returns the position of the cropped iris center. + * @return The position of the cropped iris center. */ + Point2d getCropCenter(); + + /** Returns the radius of the pupil within the cropped iris. + * @return The radius of the pupil within the cropped iris, in pixels. */ + double getCropPupilRadius(); + + /** Returns the position of the pupil center within the cropped iris. + * @return The position of the pupil center within the cropped iris. */ + Point2d getCropPupilCenter(); + +private: + /** Iris image filepath. */ + string filePath; + + /** Iris identification string. */ + string id; + + /** Iris original 3-channel content. */ + Mat originalContent; + + /** Original iris content mask. */ + Mat originalContentMask; + + /** Original iris center. */ + Point2d originalIrisCenter; + + /** Original iris radius. */ + double originalIrisRadius; + + /** Original pupil center. */ + Point2d originalPupilCenter; + + /** Original pupil radius. */ + double originalPupilRadius; + + /** Strip-normalized version of the iris. */ + Mat stripContent; + + /** Mask of the strip-normalized version of the iris. */ + Mat stripContentMask; + + /** Cropped iris 3-channel content. */ + Mat colorCrop; + + /** Cropped iris gray-scaled content. */ + Mat grayCrop; + + /** Cropped iris 3-channel gray-scaled content. */ + Mat gray3CCrop; + + /** Cropped iris mask. */ + Mat cropMask; + + /** Radius of the cropped iris. */ + double cropRadius; + + /** Position of the cropped iris center. */ + Point2d cropCenter; + + /** Radius of the pupil within the cropped iris. */ + double cropPupilRadius; + + /** Position of the pupil center within the cropped iris. */ + Point2d cropPupilCenter; + + /** Pre-processes the iris content, and sets the proper iris object attributes. + * + * @param [in] originalMask The original iris mask, in its original resolution. + * @param [in] originalStripContent The original strip-normalized version of the iris content. + * @param [in] originalStripContentMask The content mask of the original strip-normalized version of the iris. + * + * @param [in] originalCenter The original position of the center of the iris. + * @param [in] originalRadius The original radius of the iris. + * + * @param [in] originalPupilCenter The original position of the pupil center. + * @param [in] originalPupilRadius The original pupil radius of the iris. + * + * @return void */ + void preProcess( + Mat *originalMask, Mat *originalStripContent, Mat *originalStripContentMask, + Point2d originalCenter, double originalRadius, + Point2d originalPupilCenter, double originalPupilRadius); +}; + +#endif diff --git a/tshepii/src/IrisProcessor.hpp b/tshepii/src/IrisProcessor.hpp new file mode 100755 index 0000000..e9b1f1a --- /dev/null +++ b/tshepii/src/IrisProcessor.hpp @@ -0,0 +1,73 @@ +/************************************************************************* +* TSHEPII: Tool for Supporting Human Examination of Postmortem Iris Images +* Version : 1.0 +* Date : April 2018 +* Authors : Daniel Moreira, Adam Czajka, University of Notre Dame, USA +*************************************************************************/ + +#ifndef IRIS_PROCESSOR_H +#define IRIS_PROCESSOR_H + +#include + +using namespace std; +using namespace cv; + +/** Implements a processor of iris images. */ +class IrisProcessor { +public: + /** Constructor of iris processors. + * @param [in] inputIris The iris to be processed. + * @param [in] inputIrisMask A mask to be taken into account when processing the given iris. + * @param [in] outputIris The result of processing the given iris. + * @param [in] originalIris The original version of the input iris image, without any previous processing. + */ + IrisProcessor(Mat *inputIris, Mat *inputIrisMask, Mat *outputIris, Mat *originalIris) { + this->inputIris = inputIris; + this->inputIrisMask = inputIrisMask; + this->outputIris = outputIris; + this->originalIris = originalIris; + } + + /** Destructor. */ + ~IrisProcessor() { + this->inputIris = NULL; + this->inputIrisMask = NULL; + this->outputIris = NULL; + this->originalIris = NULL; + } + + /** Returns a pointer to the input iris mask. + * @return The input iris mask. */ + Mat* getInputIrisMask() { + return this->inputIrisMask; + } + + /** Updates the output iris image, after processing the input one. + * Each subclass of IrisProcessor MUST implement this method. + * @return void */ + virtual void updateOutput() { + throw runtime_error("Don't forget to implement YourClass::updateOutput()."); + } + + /** Returns the image to be added to the report document. + * @return The image to be added to the report document. */ + virtual Mat getReportImage() { + throw runtime_error("Don't forget to implement YourClass::getReportImage()."); + } + +protected: + /** Input iris that must be processed. */ + Mat *inputIris; + + /** Mask of the input image. */ + Mat *inputIrisMask; + + /** Output image that will hold the processed input image. */ + Mat *outputIris; + + /** Original version of the input iris. */ + Mat *originalIris; +}; + +#endif \ No newline at end of file diff --git a/tshepii/src/IrisSegmenter.cpp b/tshepii/src/IrisSegmenter.cpp new file mode 100755 index 0000000..c988934 --- /dev/null +++ b/tshepii/src/IrisSegmenter.cpp @@ -0,0 +1,94 @@ +/******************************************************* +* Iris image segmentation for TSHEPII +* This codes use the Open Source for Iris (OSIRIS) software +* Date : February 2018 +* Authors : Daniel Moreira, Adam Czajka +********************************************************/ + +#include "IrisSegmenter.h" + +using namespace std; +using namespace cv; +using namespace osiris; + +/** Implements the iris image segmentation and normalization using OSIRIS open software. */ +void irisSegmentation( + const Mat& inputIrisImage, + Mat& outputIrisMask, + Mat& outputIrisNormImage, + Mat& outputIrisNormMask, + CvPoint& pupilCenter, + CvPoint& irisCenter, + int& pupilRadius, + int& irisRadius, + int& minIrisDiameter, + int& minPupilDiameter, + int& maxIrisDiameter, + int& maxPupilDiameter) +{ + // Local IplImage copies of Mat buffers + IplImage iplInput = (IplImage)inputIrisImage; + IplImage* pImage = cvCloneImage(&iplInput); + IplImage* pMask = cvCreateImage(cvSize(inputIrisImage.cols, inputIrisImage.rows), IPL_DEPTH_8U, 1); + IplImage* pNormImage = cvCreateImage(cvSize(outputIrisNormImage.rows, outputIrisNormImage.cols), IPL_DEPTH_8U, 1); + IplImage* pNormMask = cvCreateImage(cvSize(outputIrisNormImage.rows, outputIrisNormImage.cols), IPL_DEPTH_8U, 1); + + // OSIRIS output arguments + OsiCircle rPupil; + OsiCircle rIris; + vector rThetaCoarsePupil; + vector rThetaCoarseIris; + vector rCoarsePupilContour; + vector rCoarseIrisContour; + + // Call OSIRIS segmentation + OsiProcessings osiProc; + osiProc.segment( + pImage, + pMask, + rPupil, + rIris, + rThetaCoarsePupil, + rThetaCoarseIris, + rCoarsePupilContour, + rCoarseIrisContour, + minIrisDiameter, + minPupilDiameter, + maxIrisDiameter, + maxPupilDiameter); + + osiProc.normalizeFromContour( + pImage, + pNormImage, + rPupil, + rIris, + rThetaCoarsePupil, + rThetaCoarseIris, + rCoarsePupilContour, + rCoarseIrisContour); + + osiProc.normalizeFromContour( + pMask, + pNormMask, + rPupil, + rIris, + rThetaCoarsePupil, + rThetaCoarseIris, + rCoarsePupilContour, + rCoarseIrisContour); + + // Set output arguments + pupilCenter = rPupil.getCenter(); + irisCenter = rIris.getCenter(); + pupilRadius = rPupil.getRadius(); + irisRadius = rIris.getRadius(); + + outputIrisMask = pMask; + outputIrisNormImage = pNormImage; + outputIrisNormMask = pNormMask; + + // Release local image buffer + cvReleaseImage(&pImage); + +} + diff --git a/tshepii/src/IrisSegmenter.h b/tshepii/src/IrisSegmenter.h new file mode 100755 index 0000000..b38a629 --- /dev/null +++ b/tshepii/src/IrisSegmenter.h @@ -0,0 +1,33 @@ +/******************************************************* +* Iris image segmentation for TSHEPII +* This codes use the Open Source for Iris (OSIRIS) software +* Date : February 2018 +* Authors : Daniel Moreira, Adam Czajka +********************************************************/ + +#ifndef IRIS_SEGMENTER_H +#define IRIS_SEGMENTER_H + +#include +#include "OsiProcessings.h" + +using namespace std; +using namespace cv; +using namespace osiris; + +/** Implements the iris image segmentation using OSIRIS open software. */ +void irisSegmentation( + const Mat& inputIrisImage, + Mat& outputIrisMask, + Mat& outputIrisNormImage, + Mat& outputIrisNormMask, + CvPoint& pupilCenter, + CvPoint& irisCenter, + int& pupilRadius, + int& irisRadius, + int& minIrisDiameter, + int& minPupilDiameter, + int& maxIrisDiameter, + int& maxPupilDiameter); + +#endif \ No newline at end of file diff --git a/tshepii/src/OsiCircle.cpp b/tshepii/src/OsiCircle.cpp new file mode 100755 index 0000000..fb86128 --- /dev/null +++ b/tshepii/src/OsiCircle.cpp @@ -0,0 +1,123 @@ +/******************************************************* +* Open Source for Iris : OSIRIS +* Version : 4.0 +* Date : 2011 +* Author : Guillaume Sutra, Telecom SudParis, France +* License : BSD +* Adapted for TSHEPII : Adam Czajka, Daniel Moreira, 2018 +********************************************************/ + +#include +#include "OsiCircle.h" + +using namespace std ; + +namespace osiris +{ + + // CONSTRUCTORS & DESTRUCTORS + ///////////////////////////// + + + OsiCircle::OsiCircle() + { + // Do nothing + } + + OsiCircle::~OsiCircle() + { + // Do nothing + } + + OsiCircle::OsiCircle ( const CvPoint & rCenter , int rRadius ) + { + setCenter(rCenter) ; + setRadius(rRadius) ; + } + + + // ACCESSORS + //////////// + + + CvPoint OsiCircle::getCenter ( ) const + { + return mCenter ; + } + + int OsiCircle::getRadius ( ) const + { + return mRadius ; + } + + void OsiCircle::setCenter(const CvPoint & rCenter ) + { + mCenter = rCenter ; + } + + void OsiCircle::setRadius ( int rRadius ) + { + if ( rRadius < 0 ) + { + throw runtime_error("Circle with negative radius : " + rRadius) ; + } + mRadius = rRadius ; + } + + void OsiCircle::setCircle ( const CvPoint & rCenter , int rRadius ) + { + setCenter(rCenter) ; + setRadius(rRadius) ; + } + + void OsiCircle::setCircle ( int rCenterX , int rCenterY , int rRadius ) + { + setCircle(cvPoint(rCenterX,rCenterY),rRadius) ; + } + + + // OPERATORS + //////////// + + + void OsiCircle::computeCircleFitting ( const vector & rPoints ) + { + // Compute the averages mx and my + float mx = 0 , my = 0 ; + for ( int p = 0 ; p < rPoints.size() ; p++ ) + { + mx += rPoints[p].x ; + my += rPoints[p].y ; + } + mx = mx / rPoints.size() ; + my = my / rPoints.size() ; + + // Work in (u,v) space, with u = x-mx and v = y-my + float u = 0 , v = 0 , suu = 0 , svv = 0 , suv = 0 , suuu = 0 , svvv = 0 , suuv = 0 , suvv = 0 ; + + // Build some sums + for ( int p = 0 ; p < rPoints.size() ; p++ ) + { + u = rPoints[p].x - mx ; + v = rPoints[p].y - my ; + suu += u * u ; + svv += v * v ; + suv += u * v ; + suuu += u * u * u ; + svvv += v * v * v ; + suuv += u * u * v ; + suvv += u * v * v ; + } + + // These equations are demonstrated in paper from R.Bullock (2006) + float uc = (float) (0.5 * ( suv * ( svvv + suuv ) - svv * ( suuu + suvv ) ) / ( suv * suv - suu * svv )); + float vc = (float) (0.5 * (suv * (suuu + suvv) - suu * (svvv + suuv)) / (suv * suv - suu * svv)); + + // Circle parameters + setCenter(cvPoint((int)(uc+mx),(int)(vc+my))) ; + setRadius((int)(sqrt(uc*uc+vc*vc+(suu+svv)/rPoints.size()))) ; + } + + +} // end of namespace + diff --git a/tshepii/src/OsiCircle.h b/tshepii/src/OsiCircle.h new file mode 100755 index 0000000..8eeb8ae --- /dev/null +++ b/tshepii/src/OsiCircle.h @@ -0,0 +1,105 @@ +/******************************************************* +* Open Source for Iris : OSIRIS +* Version : 4.0 +* Date : 2011 +* Author : Guillaume Sutra, Telecom SudParis, France +* License : BSD +* Adapted for TSHEPII : Adam Czajka, Daniel Moreira, 2018 +********************************************************/ + +#ifndef OSI_CIRCLE_H +#define OSI_CIRCLE_H + +#include +#include + + +namespace osiris +{ + /** Circle handler. + * Used by the Daugman's rubber sheet method + * @see OsiProcessings::normalize() + */ + class OsiCircle + { + + public : + + /** Default constructor. */ + OsiCircle ( ) ; + + /** Default destructor. */ + ~OsiCircle ( ) ; + + /** Overloaded constructor. + * @param rCenter Initialization of the center + * @param rRadius Initialization of the radius + */ + OsiCircle ( const CvPoint & rCenter , int rRadius ) ; + + /** Compute circle fitting by least-squares method. + * This function is called by OsiProcessings::segment() \n + * Reference : http://www.dtcenter.org/met/users/docs/write_ups/circle_fit.pdf + * @param rPoints A contour in cartesian coordinates + * @return void + * @see segment() + */ + void computeCircleFitting ( const std::vector & rPoints ) ; + + /** Get the circle center. + * @return The circle center + */ + CvPoint getCenter ( ) const ; + + + /** Get the circle radius. + * @return The circle radius + */ + int getRadius ( ) const ; + + + /** Set the circle center. + * @param rCenter The circle center + * @return void + */ + void setCenter ( const CvPoint & rCenter ) ; + + + /** Set the circle radius. + * @param rRadius The circle radius + * @return void + */ + void setRadius ( int rRadius ) ; + + /** Set the circle center and radius. + * @param rCenter The circle center + * @param rRadius The circle radius + * @return void + */ + void setCircle ( const CvPoint & rCenter , int rRadius ) ; + + /** Set the circle center and radius. + * @param rCenterX The x-coordinate of circle center + * @param rCenterY The y-coordinate of circle center + * @param rRadius The circle radius + * @return void + */ + void setCircle ( int rCenterX , int rCenterY , int rRadius ) ; + + + + private : + + /** The circle center. */ + CvPoint mCenter ; + + /** The circle radius. */ + int mRadius ; + + + } ; // end of class + +} // end of namespace + +#endif + diff --git a/tshepii/src/OsiProcessings.cpp b/tshepii/src/OsiProcessings.cpp new file mode 100755 index 0000000..52b732b --- /dev/null +++ b/tshepii/src/OsiProcessings.cpp @@ -0,0 +1,1265 @@ +/******************************************************* +* Open Source for Iris : OSIRIS +* Version : 4.0 +* Date : 2011 +* Author : Guillaume Sutra, Telecom SudParis, France +* License : BSD +* Adapted for TSHEPII : Adam Czajka, Daniel Moreira, 2018 +********************************************************/ + +#include +#include +#include "OsiProcessings.h" + +using namespace std ; + +namespace osiris +{ + + OsiProcessings::OsiProcessings() + { + // Do nothing + } + + + OsiProcessings::~OsiProcessings() + { + // Do nothing + } + + void OsiProcessings::segment ( const IplImage * pSrc , + IplImage * pMask , + OsiCircle & rPupil , + OsiCircle & rIris , + vector & rThetaCoarsePupil , + vector & rThetaCoarseIris , + vector & rCoarsePupilContour , + vector & rCoarseIrisContour , + int minIrisDiameter , + int minPupilDiameter , + int maxIrisDiameter , + int maxPupilDiameter ) + { + + // Check arguments + ////////////////// + + // String functions + // OsiStringUtils str ; + + // Temporary int to check sizes of pupil and iris + int check_size = 0 ; + + // Default value for maxIrisDiameter if user did not specify it + if ( maxIrisDiameter == 0 ) + { + maxIrisDiameter = min(pSrc->height,pSrc->width) ; + } + + // Change maxIrisDiameter if it is too big relative to image sizes + else if ( maxIrisDiameter > (int)(check_size = (int)floor((float)min(pSrc->height,pSrc->width))) ) + { + // uncomment "cout" lines if you want to have this warning displayed in command line + // cout << "Warning in function segment : maxIrisDiameter = " << maxIrisDiameter ; + // cout << " is replaced by " << check_size ; + // cout << " because image size is " << pSrc->width << "x" << pSrc->height << endl ; + + maxIrisDiameter = check_size ; + } + + // Default value for maxPupilDiameter if user did not specify it + if ( maxPupilDiameter == 0 ) + { + maxPupilDiameter = (int)(OSI_MAX_RATIO_PUPIL_IRIS * maxIrisDiameter) ; + } + + // Change maxPupilDiameter if it is too big relative to maxIrisDiameter and OSI_MAX_RATIO_PUPIL_IRIS + else if ( maxPupilDiameter > (check_size = (int)(OSI_MAX_RATIO_PUPIL_IRIS*maxIrisDiameter)) ) + { + // uncomment "cout" lines if you want to have this warning displayed in command line + // cout << "Warning in function segment : maxPupilDiameter = " << maxPupilDiameter ; + // cout << " is replaced by " << check_size ; + // cout << " because maxIrisDiameter = " << maxIrisDiameter ; + // cout << " and ratio pupil/iris is generally lower than " << OSI_MAX_RATIO_PUPIL_IRIS << endl ; + maxPupilDiameter = check_size ; + } + + // Change minIrisDiameter if it is too small relative to OSI_SMALLEST_IRIS + if ( minIrisDiameter < (check_size = OSI_SMALLEST_IRIS) ) + { + // uncomment "cout" lines if you want to have this warning displayed in command line + // cout << "Warning in function segment : minIrisDiameter = " << minIrisDiameter ; + // cout << " is replaced by " << check_size ; + // cout << " which is the smallest size for detecting iris" << endl ; + minIrisDiameter = check_size ; + } + + // Change minPupilDiameter if it is too small relative to minIrisDiameter and OSI_MIN_RATIO_PUPIL_IRIS + if ( minPupilDiameter < (int)(check_size = (int)(minIrisDiameter*OSI_MIN_RATIO_PUPIL_IRIS)) ) + { + // uncomment "cout" lines if you want to have this warning displayed in command line + // cout << "Warning in function segment : minPupilDiameter = " << minPupilDiameter ; + // cout << " is replaced by " << check_size ; + // cout << " because minIrisDiameter = " << minIrisDiameter ; + // cout << " and ratio pupil/iris is generally upper than " << OSI_MIN_RATIO_PUPIL_IRIS << endl ; + minIrisDiameter = check_size ; + } + + // Check that minIrisDiameter < maxIrisDiameter + if ( minIrisDiameter > maxIrisDiameter ) + { + throw runtime_error("Error in function segment : minIrisDiameter = " + + std::to_string(minIrisDiameter) + + " should be lower than maxIrisDiameter = " + + std::to_string(maxIrisDiameter)); + } + + // Make size odds + minIrisDiameter += ( minIrisDiameter % 2 ) ? 0 : -1 ; + maxIrisDiameter += ( maxIrisDiameter % 2 ) ? 0 : +1 ; + minPupilDiameter += ( minPupilDiameter % 2 ) ? 0 : -1 ; + maxPupilDiameter += ( maxPupilDiameter % 2 ) ? 0 : +1 ; + + + + + + // Start processing + /////////////////// + + + // Locate the pupil + detectPupil(pSrc,rPupil,minPupilDiameter,maxPupilDiameter) ; + + // Fill the holes in an area surrounding pupil + IplImage * clone_src = cvCloneImage(pSrc) ; + cvSetImageROI(clone_src,cvRect((int)(rPupil.getCenter().x-3.0/4.0*maxIrisDiameter/2.0), + (int)(rPupil.getCenter().y-3.0/4.0*maxIrisDiameter/2.0), + (int)(3.0/4.0*maxIrisDiameter), + (int)(3.0/4.0*maxIrisDiameter))) ; + fillWhiteHoles(clone_src,clone_src) ; + cvResetImageROI(clone_src) ; + + // Will contain samples of angles, in radians + vector theta ; + float theta_step = 0 ; + + + + // Pupil Accurate Contour + ///////////////////////// + + theta.clear() ; + theta_step = (float)(360.0 / OSI_PI / rPupil.getRadius()) ; + for ( float t = 0 ; t < 360 ; t += theta_step ) + { + theta.push_back(t*OSI_PI/180) ; + } + vector pupil_accurate_contour = findContour(clone_src, + rPupil.getCenter(), + theta, + rPupil.getRadius()-20, + rPupil.getRadius()+20) ; + + // Circle fitting on accurate contour + rPupil.computeCircleFitting(pupil_accurate_contour) ; + + + + + // Pupil Coarse Contour + /////////////////////// + + theta.clear() ; + theta_step = (float)(360.0 / OSI_PI / rPupil.getRadius() * 2) ; + for ( float t = 0 ; t < 360 ; t += theta_step ) + { + if ( t > 45 && t < 135 ) t += theta_step ; + theta.push_back(t*OSI_PI/180) ; + } + vector pupil_coarse_contour = findContour(clone_src, + rPupil.getCenter(), + theta, + rPupil.getRadius()-20, + rPupil.getRadius()+20) ; + + rThetaCoarsePupil = theta ; + rCoarsePupilContour = pupil_coarse_contour ; + + // Circle fitting on coarse contour + rPupil.computeCircleFitting(pupil_coarse_contour) ; + + + + + // Mask of pupil + //////////////// + + IplImage * mask_pupil = cvCloneImage(pSrc) ; + cvZero(mask_pupil) ; + drawContour(mask_pupil,pupil_accurate_contour,cvScalar(255),-1) ; + + + + + // Iris Coarse Contour + ////////////////////// + + theta.clear() ; + int min_radius = max((int)(rPupil.getRadius()/OSI_MAX_RATIO_PUPIL_IRIS),(int)(minIrisDiameter/2)) ; + int max_radius = min((int)(rPupil.getRadius()/OSI_MIN_RATIO_PUPIL_IRIS),(int)(3*maxIrisDiameter/4)) ; + theta_step = (float)(360.0 / OSI_PI / min_radius) ; + for ( float t = 0 ; t < 360 ; t += theta_step ) + { + if ( t < 180 || ( t > 225 && t < 315 ) ) t += 2*theta_step ; + theta.push_back(t*OSI_PI/180) ; + } + vector iris_coarse_contour = findContour(clone_src, + rPupil.getCenter(), + theta, + min_radius, + max_radius) ; + + rThetaCoarseIris = theta ; + rCoarseIrisContour = iris_coarse_contour ; + + // Circle fitting on coarse contour + rIris.computeCircleFitting(iris_coarse_contour) ; + + // Mask of iris + /////////////// + + IplImage * mask_iris = cvCloneImage(mask_pupil) ; + cvZero(mask_iris) ; + drawContour(mask_iris,iris_coarse_contour,cvScalar(255),-1) ; + + + + + // Iris Accurate Contour + //////////////////////// + + // For iris accurate contour, limit the search of contour inside a mask + // mask = dilate(mask-iris) - dilate(mask_pupil) + + // Dilate mask of iris by a disk-shape element + IplImage * mask_iris2 = cvCloneImage(mask_iris) ; + IplConvKernel * struct_element = cvCreateStructuringElementEx(21,21,10,10,CV_SHAPE_ELLIPSE) ; + //cvMorphologyEx(mask_iris2,mask_iris2,mask_iris2,struct_element,CV_MOP_DILATE) ; + cvDilate(mask_iris2,mask_iris2,struct_element) ; + cvReleaseStructuringElement(&struct_element) ; + + // Dilate the mask of pupil by a horizontal line-shape element + IplImage * mask_pupil2 = cvCloneImage(mask_pupil) ; + struct_element = cvCreateStructuringElementEx(21,21,10,1,CV_SHAPE_RECT) ; + //cvMorphologyEx(mask_pupil2,mask_pupil2,mask_pupil2,struct_element,CV_MOP_DILATE) ; + cvDilate(mask_pupil2,mask_pupil2,struct_element) ; + cvReleaseStructuringElement(&struct_element) ; + + // dilate(mask_iris) - dilate(mask_pupil) + cvXor(mask_iris2,mask_pupil2,mask_iris2) ; + + theta.clear() ; + theta_step = (float)(360.0 / OSI_PI / rIris.getRadius()) ; + for ( float t = 0 ; t < 360 ; t += theta_step ) + { + theta.push_back(t*OSI_PI/180) ; + } + vector iris_accurate_contour = findContour(clone_src, + rPupil.getCenter(), + theta, + rIris.getRadius()-50, + rIris.getRadius()+20, + mask_iris2) ; + + // Release memory + cvReleaseImage(&mask_pupil2) ; + cvReleaseImage(&mask_iris2) ; + + + + + + // Mask of iris based on accurate contours + ////////////////////////////////////////// + + cvZero(mask_iris) ; + drawContour(mask_iris,iris_accurate_contour,cvScalar(255),-1) ; + cvXor(mask_iris,mask_pupil,mask_iris) ; + + + // Refine the mask by removing some noise + ///////////////////////////////////////// + + // Build a safe area = avoid occlusions + IplImage * safe_area = cvCloneImage(mask_iris) ; + cvRectangle(safe_area,cvPoint(0,0),cvPoint(safe_area->width-1,rPupil.getCenter().y),cvScalar(0),-1) ; + cvRectangle(safe_area,cvPoint(0,rPupil.getCenter().y+rPupil.getRadius()), + cvPoint(safe_area->width-1,safe_area->height-1),cvScalar(0),-1) ; + struct_element = cvCreateStructuringElementEx(11,11,5,5,CV_SHAPE_ELLIPSE) ; + //cvMorphologyEx(safe_area,safe_area,safe_area,struct_element,CV_MOP_ERODE) ; + cvErode(safe_area,safe_area,struct_element) ; + cvReleaseStructuringElement(&struct_element) ; + + // Compute the mean and the variance of iris texture inside safe area + double iris_mean = cvMean(pSrc,safe_area) ; + IplImage * variance = cvCreateImage(cvGetSize(pSrc),IPL_DEPTH_32F,1) ; + cvConvert(pSrc,variance) ; + cvSubS(variance,cvScalar(iris_mean),variance,safe_area) ; + cvMul(variance,variance,variance) ; + double iris_variance = sqrt(cvMean(variance,safe_area)) ; + cvReleaseImage(&variance) ; + cvReleaseImage(&safe_area) ; + + // Build mask of noise : |I-mean| > 2.35 * variance + IplImage * mask_noise = cvCloneImage(pSrc) ; + cvAbsDiffS(pSrc,mask_noise,cvScalar(iris_mean)) ; + cvThreshold(mask_noise,mask_noise,2.35*iris_variance,255,CV_THRESH_BINARY) ; + cvAnd(mask_iris,mask_noise,mask_noise) ; + + // Fusion with accurate contours + IplImage * accurate_contours = cvCloneImage(mask_iris) ; + struct_element = cvCreateStructuringElementEx(3,3,1,1,CV_SHAPE_ELLIPSE) ; + cvMorphologyEx(accurate_contours,accurate_contours,accurate_contours,struct_element,CV_MOP_GRADIENT) ; + cvReleaseStructuringElement(&struct_element) ; + reconstructMarkerByMask(accurate_contours,mask_noise,mask_noise) ; + cvReleaseImage(&accurate_contours) ; + cvXor(mask_iris,mask_noise,pMask) ; + + // Release memory + cvReleaseImage(&mask_noise) ; + cvReleaseImage(&mask_pupil) ; + cvReleaseImage(&mask_iris) ; + + } // end of function + + + + + void OsiProcessings::normalize ( const IplImage * pSrc , + IplImage * pDst , + const OsiCircle & rPupil , + const OsiCircle & rIris ) + { + // Local variables + CvPoint point_pupil , point_iris ; + int x , y ; + float theta , radius ; + + // Set to zeros all pixels + cvZero(pDst) ; + + // Loop on columns of normalized src + for ( int j = 0 ; j < pDst->width ; j++ ) + { + // One column correspond to an angle teta + theta = (float) j / pDst->width * 2 * OSI_PI ; + + // Coordinates relative to both centers : iris and pupil + point_pupil = convertPolarToCartesian(rPupil.getCenter(),rPupil.getRadius(),theta) ; + point_iris = convertPolarToCartesian(rIris.getCenter(),rIris.getRadius(),theta) ; + + // Loop on lines of normalized src + for ( int i = 0 ; i < pDst->height ; i++ ) + { + // The radial parameter + radius = (float) i / pDst->height ; + + // Coordinates relative to both radii : iris and pupil + x = (int) ((1-radius) * point_pupil.x + radius * point_iris.x) ; + y = (int) ((1-radius) * point_pupil.y + radius * point_iris.y) ; + + // Do not exceed src size + if ( x>=0 && xwidth && y>=0 && yheight ) + { + ((uchar*)(pDst->imageData+i*pDst->widthStep))[j] = ((uchar*)(pSrc->imageData+y*pSrc->widthStep))[x] ; + } + + } + } + } + + // TODO : changer cette fonction pour normalisation avec contours + void OsiProcessings::normalizeFromContour ( const IplImage * pSrc , + IplImage * pDst , + const OsiCircle & rPupil , + const OsiCircle & rIris , + const vector rThetaCoarsePupil , + const vector rThetaCoarseIris , + const vector & rPupilCoarseContour , + const vector & rIrisCoarseContour ) + { + // Local variables + CvPoint point_pupil , point_iris ; + int x , y ; + float theta , radius ; + + // Set to zeros all pixels + cvZero(pDst) ; + + // Loop on columns of normalized src + for ( int j = 0 ; j < pDst->width ; j++ ) + { + // One column correspond to an angle teta + theta = (float) j / pDst->width * 2 * OSI_PI ; + + // Interpolate pupil and iris radii from coarse contours + point_pupil = interpolate(rPupilCoarseContour,rThetaCoarsePupil,theta) ; + point_iris = interpolate(rIrisCoarseContour,rThetaCoarseIris,theta) ; + + // Loop on lines of normalized src + for ( int i = 0 ; i < pDst->height ; i++ ) + { + // The radial parameter + radius = (float) i / pDst->height ; + + // Coordinates relative to both radii : iris and pupil + x = (int)((1-radius) * point_pupil.x + radius * point_iris.x) ; + y = (int)((1-radius) * point_pupil.y + radius * point_iris.y) ; + + // Do not exceed src size + if ( x>=0 && xwidth && y>=0 && yheight ) + { + ((uchar*)(pDst->imageData+i*pDst->widthStep))[j] = ((uchar*)(pSrc->imageData+y*pSrc->widthStep))[x] ; + } + + } + } + } + + + CvPoint OsiProcessings::interpolate ( const vector coarseContour , + const vector coarseTheta , + const float theta ) + { + float interpolation ; + int i1 , i2 ; + + if ( theta < coarseTheta[0] ) + { + i1 = (int)(coarseTheta.size()) - 1 ; + i2 = 0 ; + interpolation = ( theta - (coarseTheta[i1]-2*OSI_PI) ) / ( coarseTheta[i2] - (coarseTheta[i1]-2*OSI_PI) ) ; + } + + else if ( theta >= coarseTheta[coarseTheta.size()-1] ) + { + i1 = (int)(coarseTheta.size()) - 1 ; + i2 = 0 ; + interpolation = ( theta - coarseTheta[i1] ) / ( coarseTheta[i2]+2*OSI_PI - coarseTheta[i1] ) ; + } + + else + { + int i = 0 ; + while ( coarseTheta[i+1] <= theta ) i++ ; + i1 = i ; + i2 = i+1 ; + interpolation = ( theta - coarseTheta[i1] ) / ( coarseTheta[i2] - coarseTheta[i1] ) ; + } + + + float x = (1-interpolation) * coarseContour[i1].x + interpolation * coarseContour[i2].x ; + float y = (1-interpolation) * coarseContour[i1].y + interpolation * coarseContour[i2].y ; + + return cvPoint((int)x,(int)y) ; + } + + + void OsiProcessings::encode ( const IplImage * pSrc , + IplImage * pDst , + const vector & rFilters ) + { + // Compute the maximum width of the filters + int max_width = 0 ; + for ( int f = 0 ; f < rFilters.size() ; f++ ) + if (rFilters[f]->cols > max_width) + max_width = rFilters[f]->cols ; + max_width = (max_width-1)/2 ; + + // Add wrapping borders on the left and right of image for convolution + IplImage * resized = addBorders(pSrc,max_width) ; + + // Temporary images to store the result of convolution + IplImage * img1 = cvCreateImage(cvGetSize(resized),IPL_DEPTH_32F,1) ; + IplImage * img2 = cvCreateImage(cvGetSize(resized),pDst->depth,1) ; + + // Loop on filters + for ( int f = 0 ; f < rFilters.size() ; f++ ) + { + // Convolution + cvFilter2D(resized,img1,rFilters[f]) ; + + // Threshold : above or below 0 + cvThreshold(img1,img2,0,255,CV_THRESH_BINARY) ; + + // Form the iris code + cvSetImageROI(img2,cvRect(max_width,0,pSrc->width,pSrc->height)) ; + cvSetImageROI(pDst,cvRect(0,f*pSrc->height,pSrc->width,pSrc->height)) ; + cvCopy(img2,pDst,NULL) ; + cvResetImageROI(img2) ; + cvResetImageROI(pDst) ; + } + + // Free memory + cvReleaseImage(&img1) ; + cvReleaseImage(&img2) ; + cvReleaseImage(&resized) ; + } + + + + float OsiProcessings::match ( const IplImage * image1 , + const IplImage * image2 , + const IplImage * mask ) + { + // Temporary matrix to store the XOR result + IplImage * result = cvCreateImage(cvGetSize(image1),IPL_DEPTH_8U,1) ; + cvSet(result,cvScalar(0)) ; + + // Add borders on the image1 in order to shift it + int shift = 10 ; + IplImage * shifted = addBorders(image1,shift) ; + + // The minimum score will be returned + float score = 1 ; + + // Shift image1, and compare to image2 + for ( int s = -shift ; s <= shift ; s++ ) + { + cvSetImageROI(shifted,cvRect(shift+s,0,image1->width,image1->height)) ; + cvXor(shifted,image2,result,mask) ; + cvResetImageROI(shifted) ; + float mean = (float)((cvSum(result).val[0])/(cvSum(mask).val[0])) ; + score = min(score,mean) ; + } + + // Free memory + cvReleaseImage(&shifted) ; + cvReleaseImage(&result) ; + + return score ; + } + + + + + + + + /////////////////////////////////// + // PRIVATE METHODS + /////////////////////////////////// + + + // Convert polar coordinates into cartesian coordinates + CvPoint OsiProcessings::convertPolarToCartesian ( const CvPoint & rCenter , + int rRadius , + float rTheta ) + { + int x = (int)(rCenter.x + rRadius * cos(rTheta)) ; + int y = (int)(rCenter.y - rRadius * sin(rTheta)) ; + return cvPoint(x,y) ; + } + + + // Add left and right borders on an unwrapped image + IplImage * OsiProcessings::addBorders ( const IplImage * pImage , + int width ) + { + // Result image + IplImage * result = cvCreateImage(cvSize(pImage->width+2*width,pImage->height),pImage->depth,pImage->nChannels) ; + + // Copy the image in the center + cvCopyMakeBorder(pImage,result,cvPoint(width,0),IPL_BORDER_REPLICATE,cvScalarAll(0)) ; + + // Create the borders left and right assuming wrapping + for ( int i = 0 ; i < pImage->height ; i++ ) + { + for ( int j = 0 ; j < width ; j++ ) + { + ((uchar *)(result->imageData + i*result->widthStep))[j] = + ((uchar *)(pImage->imageData + i*pImage->widthStep))[pImage->width-width+j] ; + ((uchar *)(result->imageData + i*result->widthStep))[result->width-width+j] = + ((uchar *)(pImage->imageData + i*pImage->widthStep))[j] ; + } + } + + return result ; + } + + + // Detect and locate a pupil inside an eye image + void OsiProcessings::detectPupil ( const IplImage * pSrc , + OsiCircle & rPupil , + int minPupilDiameter , + int maxPupilDiameter ) + { + // Check arguments + ////////////////// + + // String functions + //OsiStringUtils str ; + + // Default value for maxPupilDiameter, if user did not specify it + if ( maxPupilDiameter == 0 ) + { + maxPupilDiameter = (int)(min(pSrc->height,pSrc->width) * OSI_MAX_RATIO_PUPIL_IRIS) ; + } + + // Change maxPupilDiameter if it is too big relative to the image size and the ratio pupil/iris + else if ( maxPupilDiameter > min(pSrc->height,pSrc->width) * OSI_MAX_RATIO_PUPIL_IRIS ) + { + int newmaxPupilDiameter = (int)floor(min(pSrc->height,pSrc->width)*OSI_MAX_RATIO_PUPIL_IRIS) ; + + // uncomment "cout" lines if you want to have this warning displayed in command line + // cout << "Warning in function detectPupil : maxPupilDiameter = " << maxPupilDiameter ; + // cout << " is replaced by " << newmaxPupilDiameter ; + // cout << " because image size is " << pSrc->width << "x" << pSrc->height ; + // cout << " and ratio pupil/iris is generally lower than " << OSI_MAX_RATIO_PUPIL_IRIS << endl ; + + maxPupilDiameter = newmaxPupilDiameter ; + } + + // Change minPupilDiameter if it is too small relative to OSI_SMALLEST_PUPIL + if ( minPupilDiameter < OSI_SMALLEST_PUPIL ) + { + // uncomment "cout" lines if you want to have this warning displayed in command line + // cout << "Warning in function detectPupil : minPupilDiameter = " << minPupilDiameter ; + // cout << " is replaced by " << OSI_SMALLEST_PUPIL ; + // cout << " which is the smallest size for detecting pupil" << endl ; + + minPupilDiameter = OSI_SMALLEST_PUPIL ; + } + + // Check that minPupilDiameter < maxPupilDiameter + if ( minPupilDiameter >= maxPupilDiameter ) + { + throw runtime_error("Error in function detectPupil : minPupilDiameter = " + + std::to_string(minPupilDiameter) + + " should be lower than maxPupilDiameter = " + + std::to_string(maxPupilDiameter)) ; + } + + + // Start processing + /////////////////// + + // Resize image (downsample) + float scale = (float) OSI_SMALLEST_PUPIL / minPupilDiameter ; + IplImage * resized = cvCreateImage(cvSize((int)(pSrc->width*scale),(int)(pSrc->height*scale)),pSrc->depth,1) ; + cvResize(pSrc,resized) ; + + // Rescale sizes + maxPupilDiameter = (int)(maxPupilDiameter * scale) ; + minPupilDiameter = (int)(minPupilDiameter * scale) ; + + // Make sizes odd + maxPupilDiameter += ( maxPupilDiameter % 2 ) ? 0 : +1 ; + minPupilDiameter += ( minPupilDiameter % 2 ) ? 0 : -1 ; + + // Fill holes + IplImage * filled = cvCreateImage(cvGetSize(resized),resized->depth,1) ; + fillWhiteHoles(resized,filled) ; + + // Gradients in horizontal direction + IplImage * gh = cvCreateImage(cvGetSize(filled),IPL_DEPTH_32F,1) ; + cvSobel(filled,gh,1,0) ; + + // Gradients in vertical direction + IplImage * gv = cvCreateImage(cvGetSize(filled),IPL_DEPTH_32F,1) ; + cvSobel(filled,gv,0,1) ; + + // Normalize gradients + IplImage * gh2 = cvCreateImage(cvGetSize(filled),IPL_DEPTH_32F,1) ; + cvMul(gh,gh,gh2) ; + IplImage * gv2 = cvCreateImage(cvGetSize(filled),IPL_DEPTH_32F,1) ; + cvMul(gv,gv,gv2) ; + IplImage * gn = cvCreateImage(cvGetSize(filled),IPL_DEPTH_32F,1) ; + cvAdd(gh2,gv2,gn) ; + cvPow(gn,gn,0.5) ; + cvDiv(gh,gn,gh) ; + cvDiv(gv,gn,gv) ; + + // Create the filters fh and fv + int filter_size = maxPupilDiameter ; + filter_size += ( filter_size % 2 ) ? 0 : -1 ; + CvMat * fh = cvCreateMat(filter_size,filter_size,CV_32FC1) ; + CvMat * fv = cvCreateMat(filter_size,filter_size,CV_32FC1) ; + for ( int i = 0 ; i < fh->rows ; i++ ) + { + float x = (float)(i - (filter_size-1)/2) ; + for ( int j = 0 ; j < fh->cols ; j++ ) + { + float y = (float)(j - (filter_size-1)/2) ; + if ( x != 0 || y != 0 ) + { + (fh->data.fl)[i*fh->cols+j] = y / sqrt(x*x+y*y) ; + (fv->data.fl)[i*fv->cols+j] = x / sqrt(x*x+y*y) ; + } + else + { + (fh->data.fl)[i*fh->cols+j] = 0 ; + (fv->data.fl)[i*fv->cols+j] = 0 ; + } + } + } + + // Create the mask + CvMat * mask = cvCreateMat(filter_size,filter_size,CV_8UC1) ; + + // Temporary matrix for masking the filter (later : tempfilter = filter * mask) + CvMat * temp_filter = cvCreateMat(filter_size,filter_size,CV_32FC1) ; + + double old_max_val = 0 ; + + // Multi resolution of radius + for ( int r = (OSI_SMALLEST_PUPIL-1)/2 ; r < (maxPupilDiameter-1)/2 ; r++ ) + { + // Centred ring with radius = r and width = 2 + cvZero(mask) ; + cvCircle(mask,cvPoint((filter_size-1)/2,(filter_size-1)/2),r,cvScalar(1),2) ; + + // Fh * Gh + cvZero(temp_filter) ; + cvCopy(fh,temp_filter,mask) ; + cvFilter2D(gh,gh2,temp_filter) ; + + // Fv * Gv + cvZero(temp_filter) ; + cvCopy(fv,temp_filter,mask) ; + cvFilter2D(gv,gv2,temp_filter) ; + + // Fh*Gh + Fv*Gv + cvAdd(gh2,gv2,gn) ; + cvScale(gn,gn,1.0/cvSum(mask).val[0]) ; + + // Sum in the disk-shaped neighbourhood + cvZero(mask) ; + cvCircle(mask,cvPoint((filter_size-1)/2,(filter_size-1)/2),r,cvScalar(1),-1) ; + cvFilter2D(filled,gh2,mask) ; + cvScale(gh2,gh2,-1.0/cvSum(mask).val[0]/255.0,1) ; + + // Add the two features : contour + darkness + cvAdd(gn,gh2,gn) ; + + // Find the maximum in feature image + double max_val ; + CvPoint max_loc ; + cvMinMaxLoc(gn,0,&max_val,0,&max_loc) ; + + if ( max_val > old_max_val ) + { + old_max_val = max_val ; + rPupil.setCircle(max_loc,r) ; + } + } + + // Rescale circle + int x = (int)(( (float) ( rPupil.getCenter().x * (pSrc->width-1) ) ) / (filled->width-1) + (float)((1.0/scale)-1)/2) ; + int y = (int)(( (float) ( rPupil.getCenter().y * (pSrc->height-1) ) ) / (filled->height-1) + (float)((1.0/scale)-1)/2) ; + int r = (int)(rPupil.getRadius() / scale) ; + rPupil.setCircle(x,y,r) ; + + // Release memory + cvReleaseImage(&resized) ; + cvReleaseImage(&filled) ; + cvReleaseImage(&gh) ; + cvReleaseImage(&gv) ; + cvReleaseImage(&gh2) ; + cvReleaseImage(&gv2) ; + cvReleaseImage(&gn) ; + cvReleaseMat(&fh) ; + cvReleaseMat(&fv) ; + cvReleaseMat(&mask) ; + cvReleaseMat(&temp_filter) ; + + } // end of function + + + + + // Morphological reconstruction + void OsiProcessings::reconstructMarkerByMask ( const IplImage * pMarker , + const IplImage * pMask , + IplImage * pDst ) + { + // Temporary image that will inform about marker evolution + IplImage * difference = cvCloneImage(pMask) ; + + // :WARNING: if user calls f(x,y,y) instead of f(x,y,z), the mask MUST be cloned before processing + IplImage * mask = cvCloneImage(pMask) ; + + // Copy the marker + cvCopy(pMarker,pDst) ; + + // Structuring element for morphological operation + IplConvKernel * structuring_element = cvCreateStructuringElementEx(3,3,1,1,CV_SHAPE_ELLIPSE) ; + + // Will stop when marker does not change anymore + while ( cvSum(difference).val[0] ) + { + // Remind marker before processing, in order to + // compare with the marker after processing + cvCopy(pDst,difference) ; + + // Dilate the marker + cvDilate(pDst,pDst,structuring_element) ; + + // Keep the minimum between marker and mask + cvMin(pDst,mask,pDst) ; + + // Evolution of the marker + cvAbsDiff(pDst,difference,difference) ; + } + + // Release memory + cvReleaseImage(&mask) ; + cvReleaseImage(&difference) ; + cvReleaseStructuringElement(&structuring_element) ; + + } // end of function + + + + + // Fill the white holes surrounded by dark pixels, such as specular reflection inside pupil area + void OsiProcessings::fillWhiteHoles ( const IplImage * pSrc , + IplImage * pDst ) + { + int width , height ; + if ( pSrc->roi ) + { + width = pSrc->roi->width ; + height = pSrc->roi->height ; + } + else + { + width = pSrc->width ; + height = pSrc->height ; + } + + // Mask for reconstruction : pSrc + borders=0 + IplImage * mask = cvCreateImage(cvSize(width+2,height+2),pSrc->depth,1) ; + cvZero(mask) ; + cvSetImageROI(mask,cvRect(1,1,width,height)) ; + cvCopy(pSrc,mask) ; + cvResetImageROI(mask) ; + + // Marker for reconstruction : all=0 + borders=255 + IplImage * marker = cvCloneImage(mask) ; + cvZero(marker) ; + cvRectangle(marker,cvPoint(1,1),cvPoint(width+1,height+1),cvScalar(255)) ; + + // Temporary result of reconstruction + IplImage * result = cvCloneImage(mask) ; + + // Morphological reconstruction + reconstructMarkerByMask(marker,mask,result) ; + + // Remove borders + cvSetImageROI(result,cvRect(1,1,width,height)) ; + cvCopy(result,pDst) ; + + // Release memory + cvReleaseImage(&marker) ; + cvReleaseImage(&mask) ; + cvReleaseImage(&result) ; + + } // end of function + + + + + + + // Unwrap a ring into a rectangular band + IplImage * OsiProcessings::unwrapRing ( const IplImage * pSrc , + const CvPoint & rCenter , + int minRadius , + int maxRadius , + const vector & rTheta ) + { + // Result image + IplImage * result = cvCreateImage(cvSize((int)(rTheta.size()),maxRadius-minRadius+1),pSrc->depth,1) ; + cvZero(result) ; + + // Loop on columns of normalized image + for ( int j = 0 ; j < result->width ; j++ ) + { + // Loop on lines of normalized image + for ( int i = 0 ; i < result->height ; i++ ) + { + CvPoint point = convertPolarToCartesian(rCenter,minRadius+i,rTheta[j]) ; + + // Do not exceed image size + if ( point.x >= 0 && point.x < pSrc->width && point.y >= 0 && point.y < pSrc->height ) + ((uchar *)(result->imageData+i*result->widthStep))[j] = + ((uchar *)(pSrc->imageData+point.y*pSrc->widthStep))[point.x] ; + } + } + return result ; + } + + + + + + + + // Smooth the image by anisotropic smoothing (Gross & Brajovic,2003) + void OsiProcessings::processAnisotropicSmoothing ( const IplImage * pSrc , + IplImage * pDst , + int iterations , + float lambda ) + { + // Temporary float images + IplImage * tfs = cvCreateImage(cvGetSize(pSrc),IPL_DEPTH_32F,1) ; + cvConvert(pSrc,tfs) ; + IplImage * tfd = cvCreateImage(cvGetSize(pSrc),IPL_DEPTH_32F,1) ; + cvConvert(pSrc,tfd) ; + + // Make borders dark + cvRectangle(tfd,cvPoint(0,0),cvPoint(tfd->width-1,tfd->height-1),cvScalar(0)) ; + + // Weber coefficients + float rhon , rhos , rhoe , rhow ; + + // Store pixel values + float tfsc , tfsn , tfss , tfse , tfsw , tfdn , tfds , tfde , tfdw ; + + // Loop on iterations + for ( int k = 0 ; k < iterations ; k++ ) + { + // Odd pixels + for ( int i = 1 ; i < tfs->height-1 ; i++ ) + { + for ( int j = 2-i%2 ; j < tfs->width-1 ; j = j + 2 ) + { + // Get pixels in neighbourhood of original image + tfsc = ((float*)(tfs->imageData+i*tfs->widthStep))[j] ; + tfsn = ((float*)(tfs->imageData+(i-1)*tfs->widthStep))[j] ; + tfss = ((float*)(tfs->imageData+(i+1)*tfs->widthStep))[j] ; + tfse = ((float*)(tfs->imageData+i*tfs->widthStep))[j-1] ; + tfsw = ((float*)(tfs->imageData+i*tfs->widthStep))[j+1] ; + + // Get pixels in neighbourhood of light image + tfdn = ((float*)(tfd->imageData+(i-1)*tfd->widthStep))[j] ; + tfds = ((float*)(tfd->imageData+(i+1)*tfd->widthStep))[j] ; + tfde = ((float*)(tfd->imageData+i*tfd->widthStep))[j-1] ; + tfdw = ((float*)(tfd->imageData+i*tfd->widthStep))[j+1] ; + + // Compute weber coefficients + rhon = min(tfsn,tfsc) / max(1.0,abs(tfsn-tfsc)) ; + rhos = min(tfss,tfsc) / max(1.0,abs(tfss-tfsc)) ; + rhoe = min(tfse,tfsc) / max(1.0,abs(tfse-tfsc)) ; + rhow = min(tfsw,tfsc) / max(1.0,abs(tfsw-tfsc)) ; + + // Compute LightImage(i,j) + ((float*)(tfd->imageData+i*tfd->widthStep))[j] = ( ( tfsc + lambda * + ( rhon * tfdn + rhos * tfds + rhoe * tfde + rhow * tfdw ) ) + / ( 1 + lambda * ( rhon + rhos + rhoe + rhow ) ) ) ; + } + } + + cvCopy(tfd,tfs) ; + + // Even pixels + for ( int i = 1 ; i < tfs->height-1 ; i++ ) + { + for ( int j = 1+i%2 ; j < tfs->width-1 ; j = j + 2 ) + { + // Get pixels in neighbourhood of original image + tfsc = ((float*)(tfs->imageData+i*tfs->widthStep))[j] ; + tfsn = ((float*)(tfs->imageData+(i-1)*tfs->widthStep))[j] ; + tfss = ((float*)(tfs->imageData+(i+1)*tfs->widthStep))[j] ; + tfse = ((float*)(tfs->imageData+i*tfs->widthStep))[j-1] ; + tfsw = ((float*)(tfs->imageData+i*tfs->widthStep))[j+1] ; + + // Get pixels in neighbourhood of light image + tfdn = ((float*)(tfd->imageData+(i-1)*tfd->widthStep))[j] ; + tfds = ((float*)(tfd->imageData+(i+1)*tfd->widthStep))[j] ; + tfde = ((float*)(tfd->imageData+i*tfd->widthStep))[j-1] ; + tfdw = ((float*)(tfd->imageData+i*tfd->widthStep))[j+1] ; + + // Compute weber coefficients + rhon = min(tfsn,tfsc) / max(1.0,abs(tfsn-tfsc)) ; + rhos = min(tfss,tfsc) / max(1.0,abs(tfss-tfsc)) ; + rhoe = min(tfse,tfsc) / max(1.0,abs(tfse-tfsc)) ; + rhow = min(tfsw,tfsc) / max(1.0,abs(tfsw-tfsc)) ; + + // Compute LightImage(i,j) + ((float*)(tfd->imageData+i*tfd->widthStep))[j] = ( ( tfsc + lambda * + ( rhon * tfdn + rhos * tfds + rhoe * tfde + rhow * tfdw ) ) + / ( 1 + lambda * ( rhon + rhos + rhoe + rhow ) ) ) ; + } + } + + cvCopy(tfd,tfs) ; + cvConvert(tfd,pDst) ; + + } // end of iterations k + + // Borders of image + for ( int i = 0 ; i < tfd->height ; i++ ) + { + ((uchar*)(pDst->imageData+i*pDst->widthStep))[0] = + ((uchar*)(pDst->imageData+i*pDst->widthStep))[1] ; + ((uchar*)(pDst->imageData+i*pDst->widthStep))[pDst->width-1] = + ((uchar*)(pDst->imageData+i*pDst->widthStep))[pDst->width-2] ; + } + for ( int j = 0 ; j < tfd->width ; j++ ) + { + ((uchar*)(pDst->imageData))[j] = + ((uchar*)(pDst->imageData+pDst->widthStep))[j] ; + ((uchar*)(pDst->imageData+(pDst->height-1)*pDst->widthStep))[j] = + ((uchar*)(pDst->imageData+(pDst->height-2)*pDst->widthStep))[j] ; + } + + // Release memory + cvReleaseImage(&tfs) ; + cvReleaseImage(&tfd) ; + + } // end of function + + + + + + + // Compute vertical gradients using Sobel operator + void OsiProcessings::computeVerticalGradients ( const IplImage * pSrc , IplImage * pDst ) + { + // Float values for Sobel + IplImage * result_sobel = cvCreateImage(cvGetSize(pSrc),IPL_DEPTH_32F,1) ; + + // Sobel filter in vertical direction + cvSobel(pSrc,result_sobel,0,1) ; + + // Remove negative edges, ie from white (top) to black (bottom) + cvThreshold(result_sobel,result_sobel,0,0,CV_THRESH_TOZERO) ; + + // Convert into 8-bit + double min , max ; + cvMinMaxLoc(result_sobel,&min,&max) ; + cvConvertScale(result_sobel,pDst,255/(max-min),-255*min/(max-min)) ; + + // Release memory + cvReleaseImage(&result_sobel) ; + + } // end of function + + + + + + + // Run viterbi algorithm on gradient (or probability) image and find optimal path + void OsiProcessings::runViterbi ( const IplImage * pSrc , vector & rOptimalPath ) + { + // Initialize the output + rOptimalPath.clear() ; + rOptimalPath.resize(pSrc->width) ; + + // Initialize cost matrix to zero + IplImage * cost = cvCreateImage(cvGetSize(pSrc),IPL_DEPTH_32F,1) ; + cvZero(cost) ; + + // Forward process : build the cost matrix + for ( int w = 0 ; w < pSrc->width ; w++ ) + { + for ( int h = 0 ; h < pSrc->height ; h++ ) + { + // First column is same as source image + if ( w == 0 ) + ((float*)(cost->imageData+h*cost->widthStep))[w] = + ((uchar*)(pSrc->imageData+h*pSrc->widthStep))[w] ; + + else + { + // First line + if ( h == 0 ) + ((float*)(cost->imageData+h*cost->widthStep))[w] = max( + ((float*)(cost->imageData+(h)*cost->widthStep))[w-1], + ((float*)(cost->imageData+(h+1)*cost->widthStep))[w-1]) + + ((uchar*)(pSrc->imageData+h*pSrc->widthStep))[w] ; + + // Last line + else if ( h == pSrc->height - 1 ) + { + ((float*)(cost->imageData+h*cost->widthStep))[w] = max( + ((float*)(cost->imageData+h*cost->widthStep))[w-1], + ((float*)(cost->imageData+(h-1)*cost->widthStep))[w-1]) + + ((uchar*)(pSrc->imageData+h*pSrc->widthStep))[w] ; + } + + // Middle lines + else + ((float*)(cost->imageData+h*cost->widthStep))[w] = max( + ((float*)(cost->imageData+h*cost->widthStep))[w-1],max( + ((float*)(cost->imageData+(h+1)*cost->widthStep))[w-1], + ((float*)(cost->imageData+(h-1)*cost->widthStep))[w-1])) + + ((uchar*)(pSrc->imageData+h*pSrc->widthStep))[w] ; + } + } + } + + // Get the maximum in last column of cost matrix + cvSetImageROI(cost,cvRect(cost->width-1,0,1,cost->height)) ; + CvPoint max_loc ; + cvMinMaxLoc(cost,0,0,0,&max_loc) ; + int h = max_loc.y ; + int h0 = h ; + cvResetImageROI(cost) ; + + // Store the point in output vector + rOptimalPath[rOptimalPath.size()-1] = h0 ; + + float h1 , h2 , h3 ; + + // Backward process + for ( int w = (int)(rOptimalPath.size()) - 2 ; w >= 0 ; w-- ) + { + // Constraint to close the contour + if ( h - h0 > w ) + h -- ; + else if ( h0 - h > w ) + h ++ ; + + // When no need to constraint : use the cost matrix + else + { + // h1 is the value above line h + h1 = ( h == 0 ) ? 0 : ((float*)(cost->imageData+(h-1)*cost->widthStep))[w] ; + + // h2 is the value at line h + h2 = ((float*)(cost->imageData+h*cost->widthStep))[w] ; + + // h3 is the value below line h + h3 = ( h == cost->height - 1 ) ? 0 : ((float*)(cost->imageData+(h+1)*cost->widthStep))[w] ; + + // h1 is the maximum => h decreases + if ( h1 > h2 && h1 > h3 ) + h-- ; + + // h3 is the maximum => h increases + else if ( h3 > h2 && h3 > h1 ) + h++ ; + } + + // Store the point in output contour + rOptimalPath[w] = h ; + + } + + // Release memory + cvReleaseImage(&cost) ; + + } // end of function + + + + // Find a contour in image using Viterbi algorithm and anisotropic smoothing + vector OsiProcessings::findContour ( const IplImage * pSrc , + const CvPoint & rCenter , + const vector & rTheta , + int minRadius , + int maxRadius , + const IplImage * pMask ) + { + // Output + vector contour ; + contour.resize(rTheta.size()) ; + + // Unwrap the image + IplImage * unwrapped = unwrapRing(pSrc,rCenter,minRadius,maxRadius,rTheta) ; + + // Smooth image + processAnisotropicSmoothing(unwrapped,unwrapped,100,1) ; + + // Extract the gradients + computeVerticalGradients(unwrapped,unwrapped) ; + + // Take into account the mask + if ( pMask ) + { + IplImage * mask_unwrapped = unwrapRing(pMask,rCenter,minRadius,maxRadius,rTheta) ; + IplImage * temp = cvCloneImage(unwrapped) ; + cvZero(unwrapped) ; + cvCopy(temp,unwrapped,mask_unwrapped) ; + cvReleaseImage(&temp) ; + cvReleaseImage(&mask_unwrapped) ; + } + + // Find optimal path in unwrapped image + vector optimalPath ; + runViterbi(unwrapped,optimalPath) ; + for ( int i = 0 ; i < optimalPath.size() ; i++ ) + { + contour[i] = convertPolarToCartesian(rCenter,minRadius+optimalPath[i],rTheta[i]) ; + } + + // Release memory + cvReleaseImage(&unwrapped) ; + + return contour ; + + } // end of function + + + + // Draw a contour (vector of CvPoint) on an image + void OsiProcessings::drawContour ( IplImage * pImage , const vector & rContour , const CvScalar & rColor , int thickness ) + { + // Draw INSIDE the contour if thickness is negative + if ( thickness < 0 ) + { + CvPoint * points = new CvPoint[rContour.size()] ; + for ( int i = 0 ; i < rContour.size() ; i++ ) + { + points[i].x = rContour[i].x ; + points[i].y = rContour[i].y ; + } + cvFillConvexPoly(pImage,points,(int)(rContour.size()),rColor) ; + delete [] points ; + } + + // Else draw the contour + else + { + // Draw the contour on binary mask + IplImage * mask = cvCreateImage(cvGetSize(pImage),IPL_DEPTH_8U,1) ; + cvZero(mask) ; + for ( int i = 0 ; i < rContour.size() ; i++ ) + { + // Do not exceed image sizes + int x = min(max(0,rContour[i].x),pImage->width) ; + int y = min(max(0,rContour[i].y),pImage->height) ; + + // Plot the point on image + ((uchar *)(mask->imageData+y*mask->widthStep))[x] = 255 ; + } + + // Dilate mask if user specified thickness + if ( thickness > 1 ) + { + IplConvKernel * se = cvCreateStructuringElementEx(3,3,1,1,CV_SHAPE_ELLIPSE) ; + cvDilate(mask,mask,se,thickness-1) ; + cvReleaseStructuringElement(&se) ; + } + + // Color rgb + cvSet(pImage,rColor,mask) ; + + // Release memory + cvReleaseImage(&mask) ; + + } + + } // end of function + + + +} // end of namespace + + diff --git a/tshepii/src/OsiProcessings.h b/tshepii/src/OsiProcessings.h new file mode 100755 index 0000000..04f333a --- /dev/null +++ b/tshepii/src/OsiProcessings.h @@ -0,0 +1,254 @@ +/******************************************************* +* Open Source for Iris : OSIRIS +* Version : 4.0 +* Date : 2011 +* Author : Guillaume Sutra, Telecom SudParis, France +* License : BSD +* Adapted for TSHEPII : Adam Czajka, Daniel Moreira, 2018 +********************************************************/ + +#ifndef OSI_PROCESSINGS_H +#define OSI_PROCESSINGS_H + +// pi = 3.14159 +#define OSI_PI 3.14159f + +// Sizes for iris and pupil +#define OSI_SMALLEST_PUPIL 11 +#define OSI_SMALLEST_IRIS 99 +#define OSI_MAX_RATIO_PUPIL_IRIS 0.7f +#define OSI_MIN_RATIO_PUPIL_IRIS 0.2f + + +#include +#include +#include "OsiCircle.h" + +namespace osiris +{ + + /** Image processing functions. + * Public functions are the main steps for iris recognition : + * segmentation, normalization, encoding, and matching. + * Private functions are used in the public functions. + */ + class OsiProcessings + { + + public : + + /** Default constructor. */ + OsiProcessings ( ) ; + + /** Default destructor. */ + ~OsiProcessings ( ) ; + + + /** Find inner and outer boundaries of iris. + * Detect and locate the pupil. Smooth the image using the Anisotropic Smoothing. + * Extract the radial gradients using Sobel operator. + * Find 4 optimal contours (accurate and coarse contours for pupil and iris) using the Viterbi algorithm. + * Build the binary mask of iris : each pixel is labelled as "iris" (1) or "not iris" (0). + * Compute the normalization from the coarse contours detected by the Viterbi. + * The four optional parameters allow adaptation to different database : \n + * Increase the minimum diameters and/or decrease the maximum diameters : faster, but can miss the pupil/iris that are not in the size range \n + * Decrease the minimum diameters and/or increase the maximum diameters : increase the probability of false alarm + * @param [in] pSrc An eye image + * @param [out] pMask The mask of iris. Filled by the function segment. Must be created before the function segment + * @param [out] rPupil The circle for the pupil. Filled by the function segment + * @param [out] rIris The circle for the iris. Filled by the function segment + * @param [in] minIrisDiameter The minimum diameter for segmenting the iris + * @param [in] minPupilDiameter The minimum diameter for segmenting the pupil + * @param [in] maxIrisDiameter The maximum diameter for segmenting the iris + * @param [in] maxPupilDiameter The maximum diameter for segmenting the pupil + * @return void + * @see detectPupil() , findContour() , OsiCircle::computeCircleFitting() , normalize() , OsiEye::segment() + */ + void segment ( const IplImage * pSrc , + IplImage * pMask , + OsiCircle & rPupil , + OsiCircle & rIris , + std::vector & rThetaCoarsePupil , + std::vector & rThetaCoarseIris , + std::vector & rCoarsePupilContour , + std::vector & rCoarseIrisContour , + int minIrisDiameter = OSI_SMALLEST_IRIS, + int minPupilDiameter = OSI_SMALLEST_PUPIL , + int maxIrisDiameter = 0 , + int maxPupilDiameter = 0 ) ; + + + + /** Normalize iris by Daugman's rubber sheet method. + * Use the function segment() to obtain pupil and iris contours. + * @param pSrc The source image + * @param pDst The normalized image. Must be created BEFORE this function. + * @param rPupil The circle modeling the pupil + * @param rIris The circle modeling the iris + * @return void + * @see normalizeFromContour() , segment() , encode() , OsiEye::normalize() + */ + void normalize ( const IplImage * pSrc , IplImage * pDst , const OsiCircle & rPupil , const OsiCircle & rIris ) ; + + + + /** Normalize iris by Daugman's rubber sheet method. + * Use the function segment() to obtain pupil and iris contours. + * @param pSrc The source image + * @param pDst The normalized image. Must be created BEFORE this function. + * @param rPupil The contour modeling the pupil + * @param rIris The contour modeling the iris + * @return void + * @see normalize() , segment() , encode() , OsiEye::normalize() + */ + void normalizeFromContour ( const IplImage * pSrc , + IplImage * pDst , + const OsiCircle & rPupil , + const OsiCircle & rIris , + const std::vector rThetaCoarsePupil , + const std::vector rThetaCoarseIris , + const std::vector & rPupilCoarseContour , + const std::vector & rIrisCoarseContour ) ; + + + CvPoint interpolate ( const std::vector coarseContour , + const std::vector coarseTheta , + const float theta ) ; + + + /** Encode the iris texture into a binary image. + * @param pSrc The normalized iris obtained by function normalize() + * @param pDst The binary iris code. Must be created BEFORE this function. + * @param rFilters The bank of Gabor filters used to encode the iris texture. + * @return void + * @see normalize() , match() , OsiEye::encode() + */ + void encode ( const IplImage * pSrc , IplImage * pDst , const std::vector & rFilters ) ; + + + + /** Match two iris codes. + * @param image1 First binary iris code, obtained by function encode() + * @param image2 Second binary iris code, obtained by function encode() + * @param mask Mask of matching. Same size as image1 and image2. + * @return The macthing score between 0 (completely similar) and 1 (completely different) + * @see encode() , OsiEye::match() + */ + float match ( const IplImage * image1 , const IplImage * image2 , const IplImage * mask ) ; + + /** Find a contour using anisotropic smoothing + viterbi algorithm. + * @param pSrc The source image + * @param rCenter The center around which the contour turns + * @param rTheta A vector of angles in radians + * @param minRadius The minimum radius for the contour + * @param maxRadius The maximum radius for the contour + * @param pMask An optional mask to forbid some pixels during the search of the contour + * @return The contour + * @see runViterbi() , unwrapRing() + */ + std::vector findContour(const IplImage * pSrc, const CvPoint & rCenter, const std::vector & rTheta, int minRadius, int maxRadius, const IplImage * pMask = 0); + + private : + + /** Add borders on left and right of unwrapped image. + * @param pImage The original image + * @param width The border width + * @return The new image + * @see match() , encode() + */ + IplImage * addBorders ( const IplImage * pImage , int width ) ; + + /** Convert polar coordinates to cartesian coordinates. + * @param rCenter The reference center in cartesian coordinates + * @param rRadius The radius coordinate + * @param rTheta The angle coordinate in radians + * @return The cartesian coordinates of the point + */ + CvPoint convertPolarToCartesian ( const CvPoint & rCenter , int rRadius , float rTheta ) ; + + /** Morphological reconstruction. + * @param pMarker [in] A binary image. The "on" pixels are to be reconstructed + * @param pMask [in] A binary image. The "off" pixels will not be reconstructed + * @param pDst [out] The destination image. Must be created BEFORE this function. + * @return void + * @see fillWhiteHoles() + */ + void reconstructMarkerByMask ( const IplImage * pMarker , const IplImage * pMask , IplImage * pDst ) ; + + /** Fill white holes such as spotlights (specular reflections). + * @param pSrc [in] The source image + * @param pDst [out] The destination image + * @return void + * @see reconstructMarkerByMask() + */ + void fillWhiteHoles ( const IplImage * pSrc , IplImage * pDst ) ; + + /** Detect a pupil inside an image. + * The optional arguments limit the search domain. + * @param pSrc [in] The source image + * @param rPupil [out] The detected pupil + * @param minPupilDiameter [in] The minimum diameter for detecting the pupil + * @param maxPupilDiameter [in] The maximum diameter for detecting the pupil + * @return void + * @see segment() , fillWhiteHoles() + */ + void detectPupil ( const IplImage * pSrc , OsiCircle & rPupil , int minPupilDiameter = OSI_SMALLEST_PUPIL , int maxPupilDiameter = 0 ) ; + + + /** Unwrap a ring into a rectangular band. + * @param pSrc The source image + * @param rCenter The ring center + * @param minRadius The minimum radius + * @param maxRadius The maximum radius + * @param rTheta A vector of angles in radians + * @return The unwrapped ring + * @see findContour() , runViterbi() , convertPolarToCartesian() + */ + IplImage * unwrapRing ( const IplImage * pSrc , const CvPoint & rCenter , int minRadius , int maxRadius , const std::vector & rTheta ) ; + + /** Smooth image using anisotropic smoothing. + * @param pSrc [in] The source image + * @param pDst [out] The destination image + * @param iterations [in] The number of iterations + * @param lambda [in] The smooth constraint + * @return void + * @see segment() + */ + void processAnisotropicSmoothing ( const IplImage * pSrc , IplImage * pDst , int iterations = 100 , float lambda = 1 ) ; + + /** Compute vertical gradients using Sobel operator. + * @param pSrc [in] The source image + * @param pDst [out] The destination image. Must be IPL_DEPTH_32F + * @return void + * @see findContour() + */ + void computeVerticalGradients ( const IplImage * pSrc , IplImage * pDst ) ; + + /** Run Viterbi algorithm on gradient or probability image and find the optimal path. + * This function is used by findContour function. \n + * Source image is an \b unwrapped image. This function works in polar representation. + * @param pSrc An unwrapped image + * @param rOptimalPath The series of radii + * @return void + * @see unwrapRing() + * @see convertPolarToCartesian() + * @see findContour() + */ + void runViterbi ( const IplImage * pSrc , std::vector & rOptimalPath ) ; + + /** Draw a contour (vector of CvPoint) on an image. + * @param pImage The image on which contour is drawn + * @param rContour The contour to draw + * @param rColor The color of contour + * @param thickness The contour thickness + * @return void + */ + void drawContour ( IplImage * pImage , const std::vector & rContour , const CvScalar & rColor = cvScalar(255) , int thickness = 1 ) ; + + + } ; // end of class + +} // end of namespace + +#endif + diff --git a/tshepii/src/configparser.cpp b/tshepii/src/configparser.cpp new file mode 100755 index 0000000..5c272c1 --- /dev/null +++ b/tshepii/src/configparser.cpp @@ -0,0 +1,94 @@ +// ####################################################################### +// NOTICE +// +// This software (or technical data) was produced for the U. S. Government +// and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +// (May 2014) – Alternative IV (Dec 2007) +// +// (c) 2024 The MITRE Corporation. All Rights Reserved. +// ####################################################################### + +#include +#include +#include +#include +#include +#include +#include +#include "configparser.h" + +using std::getline; +using std::ifstream; +using std::isspace; +using std::locale; +using std::map; +using std::string; +using std::stringstream; +using std::vector; + +ConfigParser::ConfigParser() {} + +// Remove whitespace from front and back of given string +void ConfigParser::strip(string &s) +{ + locale loc; + int front = 0; + int end = s.length() - 1; + while (isspace(s[front], loc)) + { + front++; + } + while (isspace(s[end], loc)) + { + end--; + } + s = s.substr(front, end - front + 1); +} + +// Returns true on sucess, false if config cannot be opened +bool ConfigParser::parseParams(string &config_file, map ¶ms) +{ + ifstream file; + string line; + string prop; + string value; + int eqIndex; + file.open(config_file.c_str()); + if (!file.is_open()) + { + return false; + } + else + { + while (getline(file, line)) + { + if (line.empty()) + { + continue; + } + // Remove all to the right of '#' (treated as comment) + line = line.substr(0, line.find("#")); + // Use '=' as delimiter separating prop and value + eqIndex = line.find("="); + prop = line.substr(0, eqIndex); + value = line.substr(eqIndex + 1, line.length()); + strip(prop); + strip(value); + params[prop] = value; + } + } + return true; +} + +void ConfigParser::parseDelimitedString(vector &delimited, + string &s, char delim) +{ + stringstream ss(s); + while (ss.good()) + { + string substr; + getline(ss, substr, delim); + delimited.push_back(substr); + } +} diff --git a/tshepii/src/configparser.h b/tshepii/src/configparser.h new file mode 100755 index 0000000..c313c7b --- /dev/null +++ b/tshepii/src/configparser.h @@ -0,0 +1,34 @@ +// ####################################################################### +// NOTICE +// +// This software (or technical data) was produced for the U. S. Government +// and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +// (May 2014) – Alternative IV (Dec 2007) +// +// (c) 2024 The MITRE Corporation. All Rights Reserved. +// ####################################################################### + +#ifndef TSHEPII_CLIENT_INCLUDE_CONFIGPARSER_H_ +#define TSHEPII_CLIENT_INCLUDE_CONFIGPARSER_H_ + +#include +#include +#include + +using std::map; +using std::string; +using std::vector; + +class ConfigParser +{ +public: + ConfigParser(); + + void strip(string &s); + + bool parseParams(string &config_file, map ¶ms); + + void parseDelimitedString(vector &delimited, string &s, char delim); +}; + +#endif // TSHEPII_CLIENT_INCLUDE_CONFIGPARSER_H_ diff --git a/tshepii/start-tshepii.sh b/tshepii/start-tshepii.sh new file mode 100644 index 0000000..4e69cb0 --- /dev/null +++ b/tshepii/start-tshepii.sh @@ -0,0 +1,24 @@ +#!/bin/sh +set -eu + +: "${ARTEMIS_USER:?ARTEMIS_USER must be set}" +: "${ARTEMIS_PASSWORD:?ARTEMIS_PASSWORD must be set}" + +template=/etc/tshepii/tshepii.conf +config=/tmp/tshepii.conf + +while IFS= read -r line || [ -n "$line" ]; do + case "$line" in + "artemis-user=\${ARTEMIS_USER}") + printf 'artemis-user=%s\n' "$ARTEMIS_USER" + ;; + "artemis-password=\${ARTEMIS_PASSWORD}") + printf 'artemis-password=%s\n' "$ARTEMIS_PASSWORD" + ;; + *) + printf '%s\n' "$line" + ;; + esac +done < "$template" > "$config" + +exec tshepii "$config" /etc/tshepii/log4cxx.conf diff --git a/web/Dockerfile b/web/Dockerfile new file mode 100644 index 0000000..50b417f --- /dev/null +++ b/web/Dockerfile @@ -0,0 +1,29 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2026 The MITRE Corporation. All Rights Reserved. +# + +FROM eclipse-temurin:17-jre-alpine-3.23 + +USER root + +RUN addgroup -S -g 433 iat \ + && adduser -S -u 431 -G iat -s /sbin/nologin iat + +WORKDIR /app + +COPY target/*.jar install/launch.sh /app/ + +RUN chown -R 431:433 /app \ + && chmod +x /app/launch.sh + +VOLUME ["/app/config"] + +USER 431:433 + +CMD ["sh", "/app/launch.sh"] diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..c28014f --- /dev/null +++ b/web/README.md @@ -0,0 +1,169 @@ +> +> NOTICE +> +> This software (or technical data) was produced for the U. S. Government and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV (May 2014) - Alternative IV (Dec 2007). +> +> (c) 2024 The MITRE Corporation. All Rights Reserved. +> + +## Web Component + +The Web component is the user interface used for the Iris Analysis Toolkit (IAT). + +## Table of Contents + +- [Web Component](#web-component) + - [Table of Contents](#table-of-contents) + - [Starting the Interface](#starting-the-interface) + - [Docker and IAT](#docker-and-iat) + - [From a Script](#from-a-script) + - [Run Java Unit Tests](#run-java-unit-tests) + - [Rebuild Docker Image](#rebuild-docker-image) + +## Starting the Interface +This interface can be started in two ways: +1. (Recommended for Users) [Docker Image via the IAT](#docker-and-iat) +2. (Recommended for Developers) [From a Script](#from-a-script) + +If you make changes to the scripts and want to create a new Docker image, see [Rebuild Docker Image](#rebuild-docker-image). + +### Docker and IAT +To start the interface with Docker, follow the [Quickstart for Users](../README.md#quickstart-for-users) instructions. + +### From a Script + +The following steps start the application in developer mode. This mode is for developers who are troubleshooting or contributing to the code. It starts the supporting services in Docker containers, while the backend and frontend are built and run from the local source tree. + +*Note: To use a GPU for the Iris Annotation component, comment out the CPU `iwp-annotation` Docker service and uncomment the GPU service in `iat/docker/docker-compose.local.yml`.* + +#### Package Installation +##### Environment Set Up +The following steps explain how to set up your development environment. If Homebrew is already installed, skip to [Install Packages](#install-packages). +1. Install Xcode Command Line Tools + 1. Open Terminal and run the following command: + ```bash + xcode-select --install + ``` + + 2. In the dialog window, confirm the installation and accept the license agreement. +2. Install Homebrew + 1. Open Terminal and run the following command: + ```bash + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + ``` + + 2. Type your admin password and hit Enter[^1] + 3. Wait until you see an "Installation successful" message. + [^1]: You won't see your keystrokes in the terminal + +##### Install Packages +Open Terminal and run the following commands to install the packages: + ```sh + brew install angular-cli # Angular Cli + brew install node@20.15.0 # Node + brew install maven # Maven + brew install yarn # yarn + ``` + +#### Start Up +1. Start the services: + 1. Follow the steps from [Set up Environment Variables](../README.md#set-up-environment-variables) + 2. Follow the [support's README](../support/README.md#change-default-iris-image) to change the required default iris image to an image of your choice + 3. In a terminal, run the following commands to start the Docker containers: + ```sh + ./composeScript.sh prune # Clear out old/unwanted data and unused containers, images, etc. + docker compose -f docker-compose.local.yml up # Starts only the supporting services in Docker containers, not the Web component. + ``` + + 4. Open a second terminal window and run the following command to load the environment variables from the `.env` file: + **Unix Commands** + ```sh + source docker/.env + ``` + **Windows Commands** + ```sh + Get-Content .\iat\docker\.env | ForEach-Object { + if ($_ -match '^\s*([^#][^=]+)=(.*)$') { + Set-Item -Path "Env:$($matches[1].Trim())" -Value $matches[2].Trim() + } + } + ``` + +2. Start the backend locally: + 1. In the second terminal window, run the following command from the main `iat/` project directory: + **After this step you can access the application at [http://localhost:8080](http://localhost:8080).** + ```sh + ./iwp-script.sh full # Runs the local backend services, including any changes just made + ``` + 2. In a web browser, navigate to `localhost:8080` + +3. **(Optional)** Start up Frontend Locally: + **You only need this step if you want access to a front end that automatically refreshes when changes are made.** + + **Note about the frontend development server:** Because of Spring Security and `webpack-dev-server` proxy behavior, the login page does not work through the development server. You may see unexpected behavior. Use one of these workarounds: + + - Disable the login page by setting `iwp.security.secured=false` in `web/src/main/resources/application.properties`. + - Log in through the backend at [http://localhost:8080](http://localhost:8080). Requests through the frontend development server at [http://localhost:4200](http://localhost:4200) should then be authenticated. + + 1. Open a third terminal window and run the following command from the `web/client/` subdirectory: + **After this step you can access the front end development server at [http://localhost:4200](http://localhost:4200).** + ```sh + yarn run local # Runs the local frontend services, including any changes just made + ``` + 2. In a web browser, navigate to `localhost:4200` + *Note: Do not forget to address the login-page issue.* + +4. **(Optional)** Start the database-management container: + **You only need this step if you want to access the database for debugging purposes.** + + 1. Open a fourth terminal window and run the following commands from the `web/` subdirectory: + ```sh + docker run --name iwp_php_admin --network iwp-simple_default -v phpmyadmin-volume:/etc/phpmyadmin/config.user.inc.php --link iwp_mysql:db -p 82:80 -d phpmyadmin/phpmyadmin #Runs the database container + ``` + 2. In a web browser, navigate to `localhost:82` + + +## Run Java Unit Tests +There are a few basic unit tests included in this project. Before running those tests, you must: +1. Set the variables in the Required Test Variables section of `src/test/resources/application-test.properties`. +2. Ensure that the MySQL and ActiveMQ containers are running. + - You can start the containers by running `docker compose up` in the `../docker/` directory. +3. **Optional:** Replace `src/test/resources/Image.png` if needed. Keep the filename unchanged, or update both references in `src/test/java/org/mitre/iwp/web/data/MessagingTests.java`. + +Once you have done that, you can run the following command: + ```bash + mvn -pl web -am -Dbuild.node.skip=true test + ``` + +## Rebuild Docker Image +### Prerequisites +To build this docker image, there are a few prerequisites. +1. Install the required packages and tools by following the [Install Packages instructions](#install-packages). +2. Build the JET dependency. + 1. Clone the JET repository from https://github.com/ebts/jet. + 2. Build JET by running the following commands from the main `iat/` project directory: + ```sh + cd jet/Jet # Navigates into the subdirectory that contains the pom.xml file + mvn install + ``` +3. Build the Web JAR by running the following commands in the main `iat/` project directory: + ```sh + # Build JAR + mvn -pl web -am -DskipTests package + + # Confirm that the JAR was built successfully. + cd web # Navigate to the web subdirectory. + ls -lah target/*.jar + ``` + *Note: This automatically builds the other local Maven modules (buffers, common, and support) required by the Web component.* + + The build JAR is written to `web/target/`. + +### Building Docker Image +To build a new Docker image, run the following command from the `web/` subdirectory: + ```sh + # Add --platform linux/amd64 for Apple Silicon Systems + docker build --platform linux/amd64 \ + -t new_web_image:YOUR_TAG_HERE \ + -f Dockerfile . + ``` diff --git a/web/client/.browserslistrc b/web/client/.browserslistrc new file mode 100644 index 0000000..939bd14 --- /dev/null +++ b/web/client/.browserslistrc @@ -0,0 +1,24 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. + +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries + +# You can see what browsers were selected by your queries by running: +# npx browserslist + + +> 0.5% +last 2 versions +Firefox ESR +not dead +not IE 9-11 # For IE 9-11 support, remove 'not'. \ No newline at end of file diff --git a/web/client/.editorconfig b/web/client/.editorconfig new file mode 100644 index 0000000..3c2dd6b --- /dev/null +++ b/web/client/.editorconfig @@ -0,0 +1,23 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/web/client/.eslintrc.json b/web/client/.eslintrc.json new file mode 100644 index 0000000..6751a1e --- /dev/null +++ b/web/client/.eslintrc.json @@ -0,0 +1,83 @@ +{ + "root": true, + "ignorePatterns": [ + "projects/**/*" + ], + "overrides": [ + { + "files": [ + "*.ts" + ], + "parserOptions": { + "project": [ + "tsconfig.json" + ], + "createDefaultProgram": true + }, + "extends": [ + "plugin:@angular-eslint/ng-cli-compat", + "plugin:@angular-eslint/ng-cli-compat--formatting-add-on", + "plugin:@angular-eslint/template/process-inline-templates" + ], + "rules": { + "prefer-arrow/prefer-arrow-functions": "off", + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "app", + "style": "kebab-case" + } + ], + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "app", + "style": "camelCase" + } + ], + "@typescript-eslint/consistent-type-definitions": "error", + "@typescript-eslint/dot-notation": "off", + "@typescript-eslint/explicit-member-accessibility": [ + "off", + { + "accessibility": "explicit" + } + ], + "brace-style": [ + "warn", + "1tbs" + ], + "id-blacklist": "off", + "id-match": "off", + "no-underscore-dangle": "off", + "valid-typeof": "error", + "@typescript-eslint/naming-convention": "off", + "max-len": "off", + "@typescript-eslint/member-ordering": "warn", + "eqeqeq": ["warn", "smart"], // We'll start warning about these two at first with the aim of eventually turning error on + "@typescript-eslint/consistent-type-assertions": ["error", { + "assertionStyle": "angle-bracket" + }], + "@typescript-eslint/no-non-null-assertion": "off", // we're not enforcing strictNullChecks in our tsconfig, so we don't need this + "@angular-eslint/component-class-suffix": "off", + "@angular-eslint/no-output-rename": "off", + "@angular-eslint/no-input-rename": "off", + "no-bitwise": "off" + } + }, + { + "files": [ + "*.html" + ], + "extends": [ + "plugin:@angular-eslint/template/recommended" + ], + "rules": { + "@angular-eslint/template/eqeqeq": "warn", + "@angular-eslint/template/no-negated-async": "warn" + } + } + ] +} diff --git a/web/client/angular.json b/web/client/angular.json new file mode 100644 index 0000000..3519a64 --- /dev/null +++ b/web/client/angular.json @@ -0,0 +1,152 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "cli": { + "analytics": "d253f017-3d2c-49cc-b175-63542c946de4", + "schematicCollections": [ + "@angular-eslint/schematics" + ] + }, + "version": 1, + "newProjectRoot": "projects", + "projects": { + "iris-workstation": { + "root": "", + "sourceRoot": "src", + "projectType": "application", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "aot": true, + "outputPath": "dist", + "index": "src/index.html", + "main": "src/main.ts", + "tsConfig": "src/tsconfig.app.json", + "polyfills": "src/polyfills.ts", + "assets": [ + "src/assets", + "src/favicon.ico", + "src/iris_review_config.json", + "src/iris_create_config.json" + ], + "styles": [ + "./node_modules/ngx-bootstrap/datepicker/bs-datepicker.css", + "./node_modules/bootstrap/dist/css/bootstrap.min.css", + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "node_modules/@fortawesome/fontawesome-free/css/all.css", + "src/css/dropzone.css", + "src/styles.css", + "src/theme.scss" + ], + "scripts": [ + "node_modules/jquery/dist/jquery.min.js", + "node_modules/paper/dist/paper-full.min.js", + "src/js/vendor/dropzone.js", + "src/js/vendor/modernizr-2.8.3-respond-1.4.2.min.js", + "src/js/vendor/jquery.ui.position.js" + ] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "anyComponentStyle", + "maximumWarning": "6kb" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ] + }, + "development": { + "aot": false, + "vendorChunk": true, + "extractLicenses": false, + "buildOptimizer": false, + "optimization": false, + "namedChunks": true, + "sourceMap": true + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "iris-workstation:build" + }, + "configurations": { + "production": { + "browserTarget": "iris-workstation:build:production" + }, + "development": { + "browserTarget": "iris-workstation:build:development" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "iris-workstation:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "karmaConfig": "./karma.conf.js", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "scripts": [ + "node_modules/jquery/dist/jquery.min.js", + "node_modules/paper/dist/paper-full.min.js", + "src/js/vendor/dropzone.js", + "src/js/vendor/modernizr-2.8.3-respond-1.4.2.min.js", + "src/js/vendor/jquery.ui.position.js" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "node_modules/@fortawesome/fontawesome-free/css/all.css", + "src/css/dropzone.css", + "src/styles.css" + ], + "assets": [ + "src/assets", + "src/favicon.ico", + "src/iris_review_config.json", + "src/iris_create_config.json" + ] + } + }, + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "src/**/*.ts", + "src/**/*.html" + ] + } + } + } + } + }, + "schematics": { + "@schematics/angular:component": { + "prefix": "app", + "style": "css" + }, + "@schematics/angular:directive": { + "prefix": "app" + } + } +} diff --git a/web/client/karma.conf.js b/web/client/karma.conf.js new file mode 100644 index 0000000..0701186 --- /dev/null +++ b/web/client/karma.conf.js @@ -0,0 +1,42 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + clearContext: false, // leave Jasmine Spec Runner output visible in browser + jasmine: { + random: false + } + }, + coverageIstanbulReporter: { + dir: require('path').join(__dirname, 'coverage'), reports: ['html', 'lcovonly'], + fixWebpackSourcePaths: true + }, + + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + browsers: ['Chrome'] + }); +}; diff --git a/web/client/package.json b/web/client/package.json new file mode 100644 index 0000000..9cb0e34 --- /dev/null +++ b/web/client/package.json @@ -0,0 +1,90 @@ +{ + "name": "iris-workstation", + "version": "1.0.0", + "license": "MIT", + "scripts": { + "ng": "ng", + "start": "ng serve --open", + "local": "ng serve -c development --proxy-config proxy.conf.json", + "build": "ng build", + "test": "ng test", + "lint": "ng lint", + "lite": "lite-server", + "tsc": "tsc", + "tsc:w": "tsc -w" + }, + "private": true, + "dependencies": { + "@angular/animations": "17.3.1", + "@angular/cdk": "^16.2.14", + "@angular/common": "17.3.1", + "@angular/compiler": "17.3.1", + "@angular/core": "17.3.1", + "@angular/forms": "17.3.1", + "@angular/material": "^16.2.14", + "@angular/platform-browser": "17.3.1", + "@angular/platform-browser-dynamic": "17.3.1", + "@angular/platform-server": "17.3.1", + "@angular/router": "17.3.1", + "axios": "1.6.8", + "body-parser": "1.20.2", + "bootstrap": "^4.6.0", + "chart.js": "4.4.2", + "core-js": "3.36.1", + "dropzone": "5.9.3", + "flatted": "3.3.1", + "jasmine-marbles": "^0.9.0", + "material-symbols": "^0.17.0", + "ng2-charts": "^5.0.4", + "ngx-bootstrap": "12.0.0", + "ngx-dropzone-wrapper": "16.0.0", + "ngx-pagination": "6.0.3", + "popper.js": "1.16.1", + "reflect-metadata": "0.2.1", + "rxjs": "7.8.1", + "short-uuid": "4.2.2", + "simple-color-picker": "^1.0.5", + "tslib": "2.6.2", + "yarn": "^1.22.22", + "zone.js": "~0.14.0" + }, + "devDependencies": { + "@angular-devkit/build-angular": "17.3.1", + "@angular-eslint/builder": "17.3.0", + "@angular-eslint/eslint-plugin": "17.3.0", + "@angular-eslint/eslint-plugin-template": "17.3.0", + "@angular-eslint/schematics": "17.3.0", + "@angular-eslint/template-parser": "17.3.0", + "@angular/cli": "17.3.1", + "@angular/compiler-cli": "17.3.0", + "@angular/language-service": "17.3.0", + "@fortawesome/fontawesome-free": "6.5.1", + "@types/dropzone": "5.7.8", + "@types/google-protobuf": "3.15.12", + "@types/jasmine": "5.1.4", + "@types/node": "20.11.30", + "@types/paper": "0.12.3", + "@typescript-eslint/eslint-plugin": "7.3.1", + "@typescript-eslint/parser": "7.3.1", + "concurrently": "8.2.2", + "eslint": "^8.33.0", + "eslint-plugin-import": "latest", + "eslint-plugin-jsdoc": "latest", + "eslint-plugin-prefer-arrow": "latest", + "express": "4.18.3", + "google-protobuf": "3.21.2", + "jasmine-core": "5.1.2", + "jasmine-spec-reporter": "7.0.0", + "jquery": "3.7.1", + "karma": "6.4.3", + "karma-chrome-launcher": "~3.2.0", + "karma-cli": "2.0.0", + "karma-coverage-istanbul-reporter": "~3.0.2", + "karma-jasmine": "5.1.0", + "karma-jasmine-html-reporter": "2.1.0", + "lite-server": "2.6.1", + "paper": "0.12.17", + "ts-node": "10.9.2", + "typescript": "^5.2.2" + } +} diff --git a/web/client/proxy.conf.json b/web/client/proxy.conf.json new file mode 100644 index 0000000..7d4976b --- /dev/null +++ b/web/client/proxy.conf.json @@ -0,0 +1,6 @@ +{ + "/api": { + "target": "http://localhost:8080", + "secure": false + } +} diff --git a/web/client/src/app/acii/acii.service.ts b/web/client/src/app/acii/acii.service.ts new file mode 100644 index 0000000..45ab2aa --- /dev/null +++ b/web/client/src/app/acii/acii.service.ts @@ -0,0 +1,115 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable, OnDestroy } from '@angular/core'; +import { Observable, timer, Subject, Subscription } from 'rxjs'; +import { switchMap, share, retry, takeUntil } from 'rxjs/operators'; +import { HttpClient } from '@angular/common/http'; +import { ServiceStatus } from '../types/service-response'; +import { AciiResponse } from './aciiResponse'; + + +@Injectable() +export class AciiService implements OnDestroy { + private aciiRequestStatus$!: Observable; + public aciiResponse$ = new Subject(); + private stopAciiPolling$ = new Subject(); + private _aciiStatusSubscription: Subscription[] = []; + + aciiServiceUrl = '/api/service/acii/'; + aciiServiceStatusUrl = '/api/service/acii/status/'; + aciiSnapshotUrl = '/api/service/acii/snapshot/' + + constructor(private http: HttpClient) { + } + + getAciiStatus(ebtsImageId: string, modifiedImageId: string): Observable { + return this.http.get(this.aciiServiceStatusUrl + ebtsImageId + '/' + modifiedImageId); + } + + getAciiResponse(imageId: string, isResubmit: boolean): Observable { + return this.http.get(this.aciiServiceUrl + imageId + '/' + isResubmit); + } + + getAciiSnapshot(imageData: string, ebtsImageId: string): Observable { + return this.http.post(this.aciiSnapshotUrl + ebtsImageId, imageData); + } + + startAciiPolling(ebtsImageId: string, modifiedImageId: string): void { + this.aciiRequestStatus$ = timer(1, 5000).pipe( //5000 = 5 second polling interval + switchMap(() => + this.getAciiStatus(ebtsImageId, modifiedImageId) + ), + retry(4), + share(), + takeUntil(this.stopAciiPolling$) + ); + } + + + stopAciiStatusPolling(imageId?: string): void { + if (imageId) { + this._aciiStatusSubscription[Number(imageId)].unsubscribe(); + } else { + this.stopAciiPolling$.next(true); + this.stopAciiPolling$.complete(); + this._aciiStatusSubscription.forEach(subscription => { + subscription.unsubscribe(); + }); + } + } + + requestAciiResponse(ebtsImageId: string, isResubmit: boolean, modifiedImageId?: string): void { + let imageId = ebtsImageId; + if (!!modifiedImageId) { + imageId = modifiedImageId; + } else { + modifiedImageId = '-1'; + } + this.startAciiPolling(ebtsImageId, modifiedImageId); + const serviceStatus = ServiceStatus; + let statusCounter = 0; + this._aciiStatusSubscription[Number(imageId)] = this.aciiRequestStatus$.subscribe( + data => { + switch (data) { + case serviceStatus.Error: { + console.log('AciiStatus ERROR'); + window.alert('ACII error'); + break; + } + case serviceStatus.Received: { + this.getAciiResponse(imageId, isResubmit).subscribe( + aciiResponse => { + if (aciiResponse.status == ServiceStatus.Received) { + aciiResponse.isResubmit = isResubmit; + this.aciiResponse$.next(aciiResponse); + this.stopAciiStatusPolling(imageId); + } + }); + break; + } + case serviceStatus.Pending: { + break; + } + case serviceStatus.NotSent: { + break; + } + default: + console.log('ACII: No Status Yet'); + break; + } + }); + } + + ngOnDestroy() { + this.stopAciiStatusPolling(); + } + +} diff --git a/web/client/src/app/acii/aciiResponse.ts b/web/client/src/app/acii/aciiResponse.ts new file mode 100644 index 0000000..ca3053e --- /dev/null +++ b/web/client/src/app/acii/aciiResponse.ts @@ -0,0 +1,28 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { ServiceResponse, ServiceStatus } from '../types/service-response'; + +export class AciiResponse extends ServiceResponse { + vertical: string; + horizontal: string; + + constructor(vertical: string, + horizontal: string, + imageId: string, + error: string, + status: ServiceStatus, + ebtsImageId: string, + isResubmit: boolean) { + super(imageId, error, status, ebtsImageId, isResubmit); + this.vertical = vertical; + this.horizontal = horizontal; + } +} diff --git a/web/client/src/app/admin/admin-filter.pipe.spec.ts b/web/client/src/app/admin/admin-filter.pipe.spec.ts new file mode 100644 index 0000000..83e7735 --- /dev/null +++ b/web/client/src/app/admin/admin-filter.pipe.spec.ts @@ -0,0 +1,40 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Profile } from '../profile/profile'; +import { User } from '../user/user'; +import { AdminFilterPipe } from './admin-filter.pipe'; + +describe('II. The AdminFilterPipe Class:', () => { + const pipe = new AdminFilterPipe(); + const profile = new Profile(); + + const user1 = new User(-1, 'jdoe', profile); + const user2 = new User(1, 'jsmith', profile); + + const userArray = [user1, user2]; + const jsonArray = [JSON.stringify(user1), JSON.stringify(user2)]; + + const filter = 'jsmith'; + const transformer = pipe.transform(userArray, filter); + const results = transformer[0].username; + + it(`a. Creates an array of User: ${jsonArray}.`, () => { + expect(transformer).toBeInstanceOf(Array); + }); + + it(`b. Filters the User array by username "${filter}."`, () => { + expect(results).toEqual(filter); + }); + + it (`c. Returns the ${transformer.length} element that contains the username ${filter}: ${jsonArray[1]}.`, () => { + expect(transformer.length).toEqual(1); + }); +}); diff --git a/web/client/src/app/admin/admin-filter.pipe.ts b/web/client/src/app/admin/admin-filter.pipe.ts new file mode 100644 index 0000000..7e23004 --- /dev/null +++ b/web/client/src/app/admin/admin-filter.pipe.ts @@ -0,0 +1,38 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Pipe, PipeTransform } from '@angular/core'; +import { User } from '../user/user'; + +@Pipe({ + name: 'adminFilter' +}) +export class AdminFilterPipe implements PipeTransform { + transform(user: User[], args?: any): any { + const sortedUsers = user.sort((p1, p2) => { + const name1 = p1.username.toLowerCase(); + const name2 = p2.username.toLowerCase(); + if(name1 > name2) { + return 1; +} + if(name1 < name2) { + return -1; +} + return 0; + }); + + if (args == ''){ + return sortedUsers; + } + + + return sortedUsers.filter(usr => usr.username.toLowerCase().indexOf(args.toLowerCase()) !== -1); + } +} diff --git a/web/client/src/app/admin/admin.component.css b/web/client/src/app/admin/admin.component.css new file mode 100644 index 0000000..c5a52cd --- /dev/null +++ b/web/client/src/app/admin/admin.component.css @@ -0,0 +1,17 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +.confirm-cancel-btn-group { + float: right; +} + +.mat-mdc-header-cell { + font-weight: bold !important; +} \ No newline at end of file diff --git a/web/client/src/app/admin/admin.component.html b/web/client/src/app/admin/admin.component.html new file mode 100644 index 0000000..97f938d --- /dev/null +++ b/web/client/src/app/admin/admin.component.html @@ -0,0 +1,130 @@ + +
+
+
+

Manage Users

+
+ + + +
+
+ + + + + + + + + + + +
User Name + {{user.username}} + it's you! + Modify + + +
+ +
+
+
+ +
+ +
+ + + +

{{ editing ? "Update" : "Create" }} User

+
+
+ + + Name must be unique + + + + Incorrect password + + + + + New password must be between 8 & 64 characters. + + + + + Passwords do not match + + +
+ + + + + + + + + +
RolesDescription
{{ role.name }} + {{ role.description }}
+
+
+
+
+ + + + + +
+
+ + + +

Delete User Account

+
+ Are you sure you'd like to delete this user? +
+
+ + +
+
diff --git a/web/client/src/app/admin/admin.component.spec.ts b/web/client/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..47075db --- /dev/null +++ b/web/client/src/app/admin/admin.component.spec.ts @@ -0,0 +1,51 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { AdminComponent } from './admin.component'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatTableModule } from '@angular/material/table'; + +describe('III. The AdminComponent Class:', () => { + let component: AdminComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, MatDialogModule, MatSnackBarModule, MatTableModule], + declarations: [AdminComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it(`a. showHidePassword takes a boolean value as a parameter (true) and makes the password invisible.`, () => { + component.passwordVisible = true; + component.showHidePassword(); + fixture.detectChanges(); + expect(component.passwordVisible).toEqual(false); + }); + + it(`b. showHidePassword takes a boolean value as a parameter (false) and makes the password visible.`, () => { + component.passwordVisible = false; + component.showHidePassword(); + fixture.detectChanges(); + expect(component.passwordVisible).toEqual(true); + }); +}); diff --git a/web/client/src/app/admin/admin.component.ts b/web/client/src/app/admin/admin.component.ts new file mode 100644 index 0000000..7ef10ea --- /dev/null +++ b/web/client/src/app/admin/admin.component.ts @@ -0,0 +1,259 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { ChangeDetectorRef, Component, ElementRef, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { User } from '../user/user'; +import { NgForm } from '@angular/forms'; +import { AdminService } from './admin.service'; +import { UserRole } from './user-role'; +import { MatTableDataSource } from '@angular/material/table'; +import { MatPaginator } from '@angular/material/paginator'; +import { ProfileService } from '../profile/profile.service'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.css'] +}) +export class AdminComponent implements OnInit { + + users: User[] = []; + + // Objects for Material Tables + usersData!: MatTableDataSource; + usersColumns = ['userName', 'actions']; + @ViewChild(MatPaginator, {static: true}) paginator!: MatPaginator; + + userRoles: UserRole[] = []; + userToCreateOrEdit: User = this.adminService.createEmptyUser(); + editing = true; + newUser = false; + passwordVisible = false; + loggedInUsername: string; + originalRoles: string[] = []; + + hasRoleChanged = false; + + confirmNewPassword = ''; + newPassword = ''; + currentPassword = ''; + + p = 1; + + @ViewChild('addProfileModal', {static: true}) + addProfileModal: ElementRef; + + @ViewChild('createForm', {static: true}) + createForm: NgForm; + + @ViewChild('removeConfirmModal', {static: true}) + removeConfirmModal: ElementRef; + + constructor(private adminService: AdminService, public modalService: MatDialog, private matSnackBar: MatSnackBar, public profileService: ProfileService) { + this.profileService.username$ + .subscribe(name => { + this.loggedInUsername = name; + }); + } + + ngOnInit() { + + this.loadProfiles(); + + this.adminService.getAllRoles().subscribe(roles => { + this.userRoles = roles; + }); + } + + isLoggedInUser(user: User): boolean { + return user.username === this.loggedInUsername; + } + + applyFilter($event: KeyboardEvent) { + var value = ($event.target as HTMLTextAreaElement).value; + this.usersData.filter = value.trim().toLowerCase(); + } + + loadProfiles() { + this.adminService.getAllUsers().subscribe(users => { + this.users = users; + this.usersData = new MatTableDataSource(users); + this.usersData.paginator = this.paginator; + }); + } + + closeProfile(form: NgForm) { + this.loadProfiles(); + form.resetForm(); + this.originalRoles = []; + this.modalService.closeAll(); + } + + updateRoles(isPasswordUpdated: boolean) { + this.userToCreateOrEdit.password = this.newPassword; + this.adminService.addUpdateUser(this.userToCreateOrEdit, false).subscribe({ + next: () => { + //reset the addProfile modal + this.userToCreateOrEdit = this.adminService.createEmptyUser(); + this.currentPassword = ''; + this.newPassword = ''; + this.confirmNewPassword = ''; + this.modalService.closeAll(); + this.loadProfiles(); + if (isPasswordUpdated) { + this.matSnackBar.open('User roles and password updated', 'Dismiss'); + } else { + this.matSnackBar.open('User roles updated', 'Dismiss'); + } + }, + error: (err) => { + if (isPasswordUpdated) { + this.matSnackBar.open('User password updated but roles were NOT updated', 'Dismiss'); + } else { + this.matSnackBar.open('User roles were not updated!', 'Dismiss'); + } + } + }); + } + + updateUser(passwordFormGroup: NgForm) { + // Check to see if the currentPassword input is correct. + this.adminService.checkPassword(this.userToCreateOrEdit.username, this.currentPassword).subscribe({ + next: (resp) => { + // If currentPassword input is correct, + // checks to see if the password is getting changed + // else it just updates the roles in the next handler + if (this.newPassword != null && this.newPassword.length > 0) { + this.adminService.updatePassword(this.userToCreateOrEdit.username, this.currentPassword, this.newPassword).subscribe({ + next: (resp) => { + // Checks to see if the roles are being changed. If it is, it will call updateRoles() + // If not, it means they only wanted to change the password and will show the popup + if (this.hasRoleChanged) { + this.updateRoles(true); + } else { + this.matSnackBar.open('User password updated'); + this.modalService.closeAll(); + } + }, + // If it couldn't update the password, it doesn't try to update the roles. + error: (err) => { + this.matSnackBar.open('User password and roles were NOT updated!'); + } + }); + } else { + this.updateRoles(false); + } + }, + // If currentPassword input is wrong, it does NOT update the user and notifies the user that the currentPassword input was wrong + error: (err) => { + passwordFormGroup.form.get('currentPasswordInput').setErrors({badPassword: true}); + this.matSnackBar.open('Password incorrect; user was not updated!'); + } + }); + } + + saveProfile(passwordFormGroup: NgForm) { + // Check if it's updating a user or creating a user + // If it's a previously created user, it goes into the IF statement and updates the user + // If it is a new user, it goes into the ELSE statement and creates the user + if (this.editing == true) { + this.updateUser(passwordFormGroup) + } else { + // Set the userToCreateOrEdit's password to the typed in password + this.userToCreateOrEdit.password = this.newPassword; + this.adminService.addUpdateUser(this.userToCreateOrEdit, true) + .subscribe({ + next: () => { + // reset the create modal + this.userToCreateOrEdit = this.adminService.createEmptyUser(); + this.currentPassword = ''; + this.newPassword = ''; + this.confirmNewPassword = ''; + this.modalService.closeAll(); + this.loadProfiles(); + this.matSnackBar.open('User created', 'Dismiss'); + }, + error: (err) => { + this.matSnackBar.open('User was NOT created!'); + } + }); + } + } + + removeProfile(user: User) { + this.adminService.removeUser(user).subscribe(response => { + this.userToCreateOrEdit = this.adminService.createEmptyUser(); + this.loadProfiles(); + this.modalService.closeAll(); + this.matSnackBar.open('User removed', 'Dismiss'); + }); + } + + profileEditClicked(user: User, modal: TemplateRef) { + this.passwordVisible = false; + this.newUser = false; + this.editing = true; + this.userToCreateOrEdit = user; + this.userToCreateOrEdit.roles.forEach(role => { + this.originalRoles.push(role); + }) + this.hasRoleChanged = false; + this.modalService.open(modal); + } + + newProfileClicked(modal: TemplateRef) { + this.passwordVisible = false; + this.newUser = true; + this.editing = false; + this.hasRoleChanged = false; + this.userToCreateOrEdit = this.adminService.createEmptyUser(); + this.modalService.open(modal); + } + + removeProfileClicked(user: User, modal: TemplateRef) { + this.userToCreateOrEdit = user; + this.modalService.open(modal); + } + + setRole(userRole: UserRole, event: any) { + let newUserRoles: string[] = []; + if (event.srcElement.checked) { + newUserRoles.push(userRole.name) + } else { + const currentIndex: number = this.userToCreateOrEdit.roles.indexOf(userRole.name); + const newIndex: number = newUserRoles.indexOf(userRole.name) + if (currentIndex !== -1) { + this.userToCreateOrEdit.roles.splice(currentIndex, 1); + } + if (newIndex !== -1) { + newUserRoles.splice(newIndex,1) + } + } + this.userToCreateOrEdit.roles = this.userToCreateOrEdit.roles.concat(newUserRoles) + this.hasRoleChanged = !this.checkRoles(); + } + + checkRoles() { + let jsonCurrent = JSON.stringify(this.userToCreateOrEdit.roles.sort()); + let jsonOriginal = JSON.stringify(this.originalRoles.sort()); + return jsonCurrent === jsonOriginal; + } + + showHidePassword() { + this.passwordVisible = !this.passwordVisible; + } + + userHasRole(role: UserRole) { + return this.userToCreateOrEdit.roles.indexOf(role.name) >= 0; + } + +} diff --git a/web/client/src/app/admin/admin.service.spec.ts b/web/client/src/app/admin/admin.service.spec.ts new file mode 100644 index 0000000..2adeb6d --- /dev/null +++ b/web/client/src/app/admin/admin.service.spec.ts @@ -0,0 +1,65 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { TestBed, inject } from '@angular/core/testing'; +import { User } from '../user/user'; +import { AdminService } from './admin.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { of } from 'rxjs'; + +describe('IV. The AdminService Class:', () => { + beforeEach(() => TestBed.configureTestingModule({ + imports: [ HttpClientTestingModule ], + providers: [ AdminService ], + schemas: [ CUSTOM_ELEMENTS_SCHEMA ] + })); + + const user = new User( + 1, + 'test', + { + id:1, + name:'test', + destinationAgencyIdentifier:'TempDAI12', + originatingAgencyIdentifier:'TempORI12', + attentionIndicator:'Attention', + sourceAgency:'SRCAGENCY', + isReportExclude:true, + fullName:'FULL NAME', + email:'EMAIL', + phoneNumber:null, + title:'TITLE', + department:'DEPARTMENT', + address:'ADDRESS', + userSetting:{ + id:1, + strokeWidth:2, + drawColor:'#127bdc', + fillColor:'#333333', + numHistory:20, + tshepiiStrokeOpacity:0 + } + } + ); + + it('a. getAllUsers returns an Observable Array of Users.', inject([AdminService], (service: AdminService) => { + const userArr = new Array(user); + + spyOn(service, 'getAllUsers').and.returnValue(of(userArr)); + const res = service.getAllUsers(); + + res.subscribe( + (value) => { + expect(value).toBeInstanceOf(Array); + } + ); + })); +}); diff --git a/web/client/src/app/admin/admin.service.ts b/web/client/src/app/admin/admin.service.ts new file mode 100644 index 0000000..0ade41c --- /dev/null +++ b/web/client/src/app/admin/admin.service.ts @@ -0,0 +1,80 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; +import { User } from '../user/user'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { UserRole } from './user-role'; +import { Profile } from '../profile/profile'; + +@Injectable( + {providedIn: 'root'} +) +export class AdminService { + + addUpdateUserUrl = '/api/admin/user/add/update/roles'; + removeUserUrl = '/api/admin/user/delete'; + getUsersUrl = '/api/admin/users'; + updateUserPasswordUrl = '/api/admin/user/password'; + checkPasswordUrl = '/api/admin/check/password'; + getRolesUrl = '/api/admin/roles'; + + constructor(private http: HttpClient) { + } + + createEmptyUser() { + return { + id: -1, + username: '', + roles: [], + password: '', + profile: new Profile(), + isNewUser: true + }; + } + + getAllUsers(): Observable { + return this.http.get(this.getUsersUrl); + } + + addUpdateUser(user: User, isNewUser: boolean): Observable { + user.isNewUser = isNewUser; + return this.http.post(this.addUpdateUserUrl, user, { + headers: new HttpHeaders({'Content-Type': 'application/json'}), + responseType: 'text' + }); + } + + removeUser(user: User): Observable { + return this.http.post(this.removeUserUrl, user, { + headers: new HttpHeaders({'Content-Type': 'application/json'}), + responseType: 'text' + }); + } + + checkPassword(userName: string, oldPassword: string) { + return this.http.post(this.checkPasswordUrl, {userName, oldPassword}, { + headers: new HttpHeaders({'Content-Type': 'application/json'}), + responseType: 'text' + }); + } + + updatePassword(userName: string, oldPassword: string, newPassword: string) { + return this.http.post(this.updateUserPasswordUrl, {userName, oldPassword, newPassword}, { + headers: new HttpHeaders({'Content-Type': 'application/json'}), + responseType: 'text' + }); + } + + getAllRoles(): Observable { + return this.http.get(this.getRolesUrl); + } +} diff --git a/web/client/src/app/admin/privilege.ts b/web/client/src/app/admin/privilege.ts new file mode 100644 index 0000000..1c0e874 --- /dev/null +++ b/web/client/src/app/admin/privilege.ts @@ -0,0 +1,21 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +export class Privilege { + id: number; + name: string; + description: string; + + constructor(id, name, description) { + this.id = id; + this.name = name; + this.description = description; + } +} diff --git a/web/client/src/app/admin/user-role.ts b/web/client/src/app/admin/user-role.ts new file mode 100644 index 0000000..c5262fd --- /dev/null +++ b/web/client/src/app/admin/user-role.ts @@ -0,0 +1,24 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Privilege } from './privilege'; + +export class UserRole { + id: number; + name: string; + description: string; + privileges: Privilege[] = []; + + constructor(id, name, description) { + this.id = id; + this.name = name; + this.description = description; + } +} diff --git a/web/client/src/app/annotation-dialog/annotation.component.css b/web/client/src/app/annotation-dialog/annotation.component.css new file mode 100644 index 0000000..934587c --- /dev/null +++ b/web/client/src/app/annotation-dialog/annotation.component.css @@ -0,0 +1,107 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +.icon-align-middle{ + vertical-align: bottom; +} + +.list-container { + padding: 3px 3px 44px 3px; +} + +.collapse-toggle-btn { + padding: 0; +} + +.list-group { + list-style-type: none; + overflow-y: auto; + box-shadow: none; + margin-bottom: 5px; +} + +.list-group .list-group { + /* Add depth indicator to sublayers only */ + border-left: 1px dashed #bbb; + margin-left: 15px; + margin-bottom: 0; + padding-bottom: 0; +} + +.list-group-item { + padding: 3px 0 0 0; + border: 0; + /*border-radius: 0;*/ +} + +.item { + padding: 2px 2px 3px 0; +} + +.item-icon { + width: 26px; + text-align: center; +} + +.item-title { + display: table; + border-collapse: collapse; + width: 100%; + margin-bottom: 0; + padding-bottom: 0; +} + + +.item.item-selected, +.item.item-selected .item { + background-color: rgb(217, 237, 247); +} + +.item.item-defocused { + background-color: #eaeaea; +} + +.item.item-selected .item, +.item.item-defocused .item { + opacity: 0.5; +} + +.item-title > * { + position: relative; + display: table-cell; +} + +.item-title > input { + box-shadow: none; + border: none; + outline: none; + width: 96%; + padding: 3px; +} + +.list-group-item-text { + text-align: left; + padding-left: 30px; +} + +.list-group-item-defocus { + background-color: #e0e0e0; +} + +footer { + position: absolute; + bottom: 0; + right: 0; + left: 0; + background-color: rgb(247 247 247); + border-radius: 0; + text-align: center; + padding: 5px; +} diff --git a/web/client/src/app/annotation-dialog/annotation.component.html b/web/client/src/app/annotation-dialog/annotation.component.html new file mode 100644 index 0000000..8dbbd85 --- /dev/null +++ b/web/client/src/app/annotation-dialog/annotation.component.html @@ -0,0 +1,67 @@ + + +
    +
  • +
    +
    +
    + +
    +
    + + + + +
    + + + + + +
    +
    + {{ getAnnotationText(item) }} +
    +
    + + + + +
  • +
+
+
+ +
+ +
+ + + +
+ diff --git a/web/client/src/app/annotation-dialog/annotation.component.spec.ts b/web/client/src/app/annotation-dialog/annotation.component.spec.ts new file mode 100644 index 0000000..aeba0d3 --- /dev/null +++ b/web/client/src/app/annotation-dialog/annotation.component.spec.ts @@ -0,0 +1,93 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core'; +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { Annotation } from '../types/annotation'; +import { AnnotationComponent } from './annotation.component'; +import { AnnotationType } from '../types/annotation'; +import { ToolManagerService } from '../iris-display/tool-manager.service'; +import { LinkedMap } from '../types/linked-map'; + +describe('V. The AnnotationComponent Class: ', () => { + let component: AnnotationComponent; + let fixture: ComponentFixture; + let toolManager: any; + + const annoType = AnnotationType.POINT; + const anno = new Annotation('string', annoType, 'string'); + const index = 1; + const event: EventEmitter = new EventEmitter(); + const valStr = 'foo'; + + beforeEach(waitForAsync(() => { + toolManager = { + selectedAnnotation: undefined, + userParentGroupId: '', + userAnnoGroup: [], + pullAnnoButtonClicked: false, + userParentGroup: undefined + }; + + TestBed.configureTestingModule({ + imports: [MatExpansionModule], + declarations: [ AnnotationComponent ], + providers: [ + {provide: ToolManagerService, useValue: toolManager} + ], + schemas: [ CUSTOM_ELEMENTS_SCHEMA ] + }) + .compileComponents(); + })); + + beforeEach(waitForAsync(() => { + fixture = TestBed.createComponent(AnnotationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + })); + + it('a. getAnnotationText takes an object instance of the Annotation class as a parameter and returns a string.', () => { + spyOn(component, 'getAnnotationText').and.returnValue(valStr); + + const res = component.getAnnotationText(anno); + expect(res).toEqual(valStr); + }); + + it('b. removeItem takes an object instance of the Annotation class as a parameter and returns a void type.', () => { + anno.parentId = 'foo'; + spyOn(component, 'removeItem').and.returnValue(undefined); + + const res = component.removeItem(anno); + expect(res).toBe(undefined); + }); + // These three test "void" functions and will require some added nuance. + it('c. isAnyParentSelected takes an object instance of the Annotation class as a parameter and returns a boolean value (false).', () => { + spyOn(component, 'isAnyParentSelected').and.returnValue(false); + + const res = component.isAnyParentSelected(anno); + expect(res).toBe(false); + }); + + it('d. removeSelectedChildren takes an object instance of the Annotation class as a parameter and removes selected children from the param if present.', () => { + anno.children = new LinkedMap(); + spyOn(component, 'removeSelectedChildren').and.returnValue(undefined); + + const res = component.removeSelectedChildren(anno); + expect(res).toBe(undefined); + }); + + it('e. selectItem takes an object instance of the Annotation class, an index, and an event as parameters.', () => { + spyOn(component, 'selectItem'); + const res = component.selectItem(anno, index, event); + expect(res).toBe(undefined); + }); +}); diff --git a/web/client/src/app/annotation-dialog/annotation.component.ts b/web/client/src/app/annotation-dialog/annotation.component.ts new file mode 100644 index 0000000..9b02e47 --- /dev/null +++ b/web/client/src/app/annotation-dialog/annotation.component.ts @@ -0,0 +1,282 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component, EventEmitter, HostListener, Input, Output } from '@angular/core'; +import { Annotation, AnnotationType } from '../types/annotation'; +import { LinkedMap } from '../types/linked-map'; +import { ToolManagerService } from '../iris-display/tool-manager.service'; + +@Component({ + selector: 'app-annotation', + templateUrl: './annotation.component.html', + styleUrls: ['./annotation.component.css'] +}) +export class AnnotationComponent { + + @Input() annotations = new LinkedMap(); + @Input() itemsMap = {}; + @Input('hiddenAnnotations') hidden = new Set(); + @Input('color') annotationColor = '#ff0000'; + @Input() isShowAnnotationLabels = false; + @Input() annotationTextSize = 12; + + @Output('remove') removeEvent = new EventEmitter(); + @Output() annotationMouseOn = new EventEmitter(); + @Output() annotationMouseOff = new EventEmitter(); + @Output('hideDialog') hideEvent = new EventEmitter(); + @Output() updateColor = new EventEmitter(); + @Output() showAnnotationColor = new EventEmitter(); + @Output() updateAnnotationTextSize = new EventEmitter(); + @Output() showHideAnnotations = new EventEmitter(); + @Output() deleteAnnotations = new EventEmitter(); + @Output() groupAnnotations = new EventEmitter(); + @Output() showHideAnnotation = new EventEmitter(); + @Output() selectedAnnotationChanged = new EventEmitter(); + + public selectedIds: Array = []; + annoSelected = false; + defocusLayers = false; + showAnnotations = true; + isDropdownToggled = false; + selectedAnnotation: Annotation; + private selected = {}; + constructor(protected toolManager: ToolManagerService + ) { + } + + getAnnotationText(annotation: Annotation) { + return annotation.getValueString(); + } + + removeItem(annotation: Annotation) { + this.toolManager.selectedAnnotation = undefined; + if (annotation.parentId != null) { + this.removeEvent.emit(annotation.id); + } else { + this.removeEvent.emit(annotation.id); + this.toolManager.userParentGroupId = ''; + this.toolManager.userAnnoGroup = []; + } + if (annotation.text == 'Iris Annotation Service') { + this.toolManager.pullAnnoButtonClicked = false; + } + } + + itemMouseOn(id: string): void { + this.annotationMouseOn.emit(id); + } + + itemMouseOff(id: string): void { + this.annotationMouseOff.emit(id); + } + + showHideAnnotationsEvent(event) { + this.showAnnotations = !this.showAnnotations; + this.showHideAnnotations.emit(this.showAnnotations); + } + + removeAllAnnotations() { + this.deleteAnnotations.emit(true); + } + + + isAnyParentSelected(anno): boolean { + if (this.selectedIds.includes(anno.id)) { + return true; + } + + if (!anno.parentId) { + return false; + } + + return this.isAnyParentSelected(this.itemsMap[anno.parentId]['annotation']); + } + + removeSelectedChildren(anno) { + if (anno.hasChildren()) { + for (const child of anno.children) { + const foundIndex = this.selectedIds.indexOf(child.id); + if (foundIndex >= 0) { + this.selectedIds.splice(foundIndex, 1); + } + this.removeSelectedChildren(child); + } + } + } + + selectItem(anno, index, event) { + const id = anno.id; + const parentId = anno.parentId || 'no_parent'; + + const foundIndex = this.selectedIds.indexOf(id); + let select = false; + if (foundIndex >= 0) { // If the item was already selected + + // If it's not a multi select, remove everything + // and add the currently selected item. + // If it is, just remove the selected item. + if (!(event.ctrlKey || event.metaKey)) { + this.selectedIds = []; + this.selectedIds.push(id); + this.selected = {}; + select = true; + } else { + this.selectedIds.splice(foundIndex, 1); + if (this.selected[parentId]) { + // Remove child with id=`id` + this.selected[parentId] = + this.selected[parentId].filter(obj => obj['id'] !== id); + } + } + } else { + // If it's not a multi-select and the clicked anno + // is not yet selected, empty the selection list + // Also check for metaKey for macs + if (!(event.ctrlKey || event.metaKey)) { + this.selectedIds = []; + this.selected = {}; + } + + // Make sure to remove all children of this particular + // item, since it's invalid to select both group and its + // children. + this.removeSelectedChildren(anno); + delete this.selected[anno.id]; + + // If any of item's parents are already selected, + // don't select this item. + if (!this.isAnyParentSelected(anno)) { + this.selectedIds.push(id); + select = true; + } + } + this.annoSelected = true; + + // If the specified item is to be selected, + // insert it to the selected map + if (select) { + if (!this.selected[parentId]) { + this.selected[parentId] = []; + } + + this.selected[parentId].push({id, index}); + } + + if (!anno.isSystemGenerated) { + if (anno.type == AnnotationType.GROUP) { + this.selectedAnnotation = anno; + this.selectedAnnotationChanged.emit(anno); + } else { + this.selectedAnnotation = this.itemsMap[id]['annotation']; + this.selectedAnnotationChanged.emit(this.selectedAnnotation); + } + } else { + this.selectedAnnotation = undefined; + this.selectedAnnotationChanged.emit(undefined); + } + } + + group() { + // Sort each parent's children list by index + Object.keys(this.selected).forEach(parentId => { + this.selected[parentId].sort((a, b) => a.index - b.index); + }); + this.groupAnnotations.emit(this.selected); + this.selectedIds.length = 0; + this.selected = {}; + + } + + expandAllGroups() { + this.collapsingExpandingAction(true); + } + + collapseAllGroups() { + this.collapsingExpandingAction(false); + } + + collapsingExpandingAction(isExpand: boolean) { + const entries = Object.entries(this.itemsMap); + + for (const [id, objects] of entries) { + const objs = Object.entries(objects); + const annotation: any = objs[0]; + const item: any = objs[1]; + + if (item[1] != undefined) { + if (item[1]._class == 'Group') { + annotation[1].isDropdownToggled = isExpand; + } + } + } + } + + showHideItem(id: string) { + //this if/else prevents 'children' being unhiden if the parent node is hiden + const anno = this.itemsMap[id]['annotation']; + if (!(this.hidden.has(anno.parentId))) { + this.showHideAnnotation.emit(id); + } + } + + isAnnotationHidden(id: string) { + return this.hidden.has(id); + } + + isHideAble(anno: Annotation) { + return anno.type == AnnotationType.POLYGON || + anno.type == AnnotationType.POINT || + anno.type == AnnotationType.PENCIL || + anno.type == AnnotationType.CIRCLE; + } + + toggleClicked(item) { + item.isDropdownToggled = !item.isDropdownToggled; + } + + clickedInside = false; + + @HostListener('document:click', ['$event']) + onDocumentClick(e) { + if (this.clickedInside) { + // If clicked within the dialogue and outside the layer, + // and the previous click wasn't outside the dialogue, + // deselect all layers. + // Basically, if clicked anywhere else but the layer, + // deselect, except the first click inside after an outside click. + if (!this.annoSelected && !this.defocusLayers) { + this.selectedIds.length = 0; + this.selected = {}; + this.selectedAnnotation = this.toolManager.userParentGroup; + this.selectedAnnotationChanged.emit(this.toolManager.userParentGroup); + } + this.defocusLayers = false; + } else if (!this.clickedInside) { + // If clicked outside the dialogue, the dialogue is + // hidden, and there were selected layers, deselect + // everything. + if (this.selectedIds.length) { + this.selectedIds.length = 0; + this.selected = {}; + } + this.defocusLayers = true; + } + this.clickedInside = false; + this.annoSelected = false; + } + + // This is needed for elements rendered thru ngIf. + // Without it, layers (esp. groups) that are conditioned + // on ngIf fire an outside click event. + @HostListener('click', ['$event']) + onClick(e) { + this.clickedInside = true; + } +} diff --git a/web/client/src/app/annotation-history-dialog/annotation-history.component.css b/web/client/src/app/annotation-history-dialog/annotation-history.component.css new file mode 100644 index 0000000..e70520f --- /dev/null +++ b/web/client/src/app/annotation-history-dialog/annotation-history.component.css @@ -0,0 +1,74 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +.expand-btn { + position: absolute; + right: 5px; + bottom: 5px; +} + +.list-group { + list-style-type: none; + overflow-y: auto; + box-shadow: none; + padding: 5px; +} + +.list-group-item { + border: 0; +} + +.list-group-item.state-wrapper { + padding: 5px 10px; +} + +.list-group-item.state-wrapper ~ .list-group-item.state-wrapper { + margin-top: 5px; + border-top: 1px solid #ddd; +} + +.list-group-item-text { + text-align: left; +} + +.anno-wrapper { + padding-bottom: 0; + padding-left: 3px; + border-left: 1px dashed #bbb; + margin-bottom: 0; +} + +.anno { + font-size: 13px; + padding: 3px 3px 3px 0; +} + +.anno .anno { + /* Make nested annos have no bottom padding */ + padding-bottom: 0; +} + +.list-group-item.anno { + padding: 5px; +} + +.revert-btn { + color: rgb(40, 167, 69); + float: right; + cursor: pointer; +} + +.card-footer { + position: absolute; + bottom: 0; + right: 0; + left: 0; + height: 50px; +} diff --git a/web/client/src/app/annotation-history-dialog/annotation-history.component.html b/web/client/src/app/annotation-history-dialog/annotation-history.component.html new file mode 100644 index 0000000..8fe1cff --- /dev/null +++ b/web/client/src/app/annotation-history-dialog/annotation-history.component.html @@ -0,0 +1,68 @@ + +
    + + + + + + + + + + +
  • +
    +
    + {{ getStateName(state.type) }} + +
    +
    + + {{ getStateText(state) }} +
    +
    + + + +
  • +
    + + + +
      + +
    • + + + + {{ getAnnotationText(itemsMap[obj.id]['annotation']) }} + + + + + + + + + +
    • +
      +
    +
    +
diff --git a/web/client/src/app/annotation-history-dialog/annotation-history.component.ts b/web/client/src/app/annotation-history-dialog/annotation-history.component.ts new file mode 100644 index 0000000..d38502b --- /dev/null +++ b/web/client/src/app/annotation-history-dialog/annotation-history.component.ts @@ -0,0 +1,106 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { State, StateType } from '../types/state'; +import { Annotation } from '../types/annotation'; + +@Component({ + selector: 'app-annotation-history', + templateUrl: './annotation-history.component.html', + styleUrls: ['./annotation-history.component.css'] +}) +export class AnnotationHistoryComponent { + + @Input() history: { curr?: State; past?: State[] } = {}; + @Input() itemsMap: any = {}; + @Output('revert') revertEvent = new EventEmitter(); + + constructor() { + } + + getStateName(type: StateType) { + switch (type) { + case StateType.ADD: + return 'Add'; + case StateType.REMOVE: + return 'Remove'; + case StateType.BULK_REMOVE: + return 'Bulk Remove'; + case StateType.RESET: + return 'Reset'; + case StateType.CLEAR: + return 'Clear'; + case StateType.GROUP_ONLY: + return 'Group'; + default: + return 'Unknown'; + } + } + + isGroup(type: StateType) { + return type == StateType.GROUP_ONLY; + } + + isAddRemove(type: StateType) { + return type == StateType.ADD || type == StateType.REMOVE; + } + + getStateText(state: State) { + switch (state.type) { + case StateType.ADD: + case StateType.REMOVE: + return this.getAnnotationText(state.annotation); + case StateType.GROUP_ONLY: + return 'Group annotation(s) together'; + case StateType.BULK_REMOVE: + return 'Remove all drawn annotation(s)'; + case StateType.RESET: + return 'Reset to last saved point'; + case StateType.CLEAR: + return 'Clear all annotation(s)'; + default: + return ''; + } + } + + getAnnotationText(annotation: Annotation): string { + return annotation.getValueString(); + } + + + getStateWithAnnotationId(id: string) { + for (const state of this.history.past) { + if (state.annotation.id === id) { + return state; + } + } + if (this.history.curr.annotation.id === id) { + return this.history.curr; + } + return null; + } + + revertTo(index: number) { + this.revertEvent.emit(index); + } + + dummyPrint(thing) { + console.log(thing); + } + + getItemsKeys(items: any) { + if (items == undefined) { + return undefined; + } + + return Object.keys(items); + } +} diff --git a/web/client/src/app/app.component.css b/web/client/src/app/app.component.css new file mode 100644 index 0000000..56b92ed --- /dev/null +++ b/web/client/src/app/app.component.css @@ -0,0 +1,25 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +.dropdown-item, .site-title { + cursor: pointer; +} + +#app-toolbar { + position: fixed; + top: 0; + z-index: 100; + box-shadow: 0 3px 5px -1px rgba(0, 0, 0, .2), 0 6px 10px 0 rgba(0, 0, 0, .14), 0 1px 18px 0 rgba(0, 0, 0, .12); +} + +#center-content { + margin-top: 80px; +} + diff --git a/web/client/src/app/app.component.html b/web/client/src/app/app.component.html new file mode 100644 index 0000000..2013f5b --- /dev/null +++ b/web/client/src/app/app.component.html @@ -0,0 +1,62 @@ + + + Iris Analysis Toolkit + + + + + + + + + + + + +
+ + +
+
+ +
+
+ + + + + +
+
+ +
+
+
+
+ + +
diff --git a/web/client/src/app/app.component.spec.ts b/web/client/src/app/app.component.spec.ts new file mode 100644 index 0000000..a38c1a2 --- /dev/null +++ b/web/client/src/app/app.component.spec.ts @@ -0,0 +1,104 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { AppComponent } from './app.component'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Profile, UserSetting } from './profile/profile'; +import { ProfileService } from './profile/profile.service'; +import { ServiceStatusService } from './service-status/service-status.service'; +import { NotificationsService } from './notifications/notifications.service'; +import { LogonInfo } from './types/logonInfo'; +import { MatDialog } from '@angular/material/dialog'; +import { BehaviorSubject, of } from 'rxjs'; + +describe('VI. The AppComponent Class: ', () => { + let fixture: ComponentFixture; + let app: AppComponent; + let profileService: jasmine.SpyObj; + let serviceStatusService: jasmine.SpyObj; + + const profile: Profile = { + id: '12345', + name: 'John Doe', + destinationAgencyIdentifier: '67890', + originatingAgencyIdentifier: '101112', + attentionIndicator: 'Attention!', + sourceAgency: 'MITRE', + isReportExclude: false, + fullName: 'John D Doe', + email: 'jdoe@iwp.org', + phoneNumber: '555-5555', + title: 'Duke', + department: 'Sporting Goods', + address: '123 Street St, A City, A State', + userSetting: new UserSetting(), + permissions: 5 + }; + + beforeEach(waitForAsync(() => { + profileService = jasmine.createSpyObj( + 'ProfileService', + ['getProfile', 'getUserPrivileges', 'setCurrentProfile'], + { + username$: new BehaviorSubject('jdoe'), + currentProfile$: new BehaviorSubject(profile) + } + ); + profileService.getProfile.and.returnValue(of(profile)); + profileService.getUserPrivileges.and.returnValue(of({CREATE_SEARCH: 'CREATE_SEARCH'})); + + serviceStatusService = jasmine.createSpyObj( + 'ServiceStatusService', + ['updateServiceVersions'] + ); + + TestBed.configureTestingModule({ + declarations: [ + AppComponent + ], + imports: [ RouterTestingModule ], + providers: [ + {provide: ProfileService, useValue: profileService}, + {provide: ServiceStatusService, useValue: serviceStatusService}, + {provide: NotificationsService, useValue: {}}, + {provide: LogonInfo, useValue: {getIsSecure: () => of(false)}}, + {provide: MatDialog, useValue: {}} + ], + schemas: [ NO_ERRORS_SCHEMA ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AppComponent); + app = fixture.componentInstance; + }); + + it('a. Creates the app.', () => { + expect(app).toBeTruthy(); + }); + + it('b. Has \'Iris Workstation\' as its title.', () => { + expect(app.title).toEqual('Iris Workstation'); + }); + + it('c. Renders its title in a span tag', () => { + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('span').textContent).toContain('Iris Analysis Toolkit'); + }); + + it(`d. profileSelected takes a profile like ${JSON.stringify(profile)} as a parameter and returns a void type.`, () => { + const res = app.profileSelected(profile); + expect(res).toBeUndefined(); + expect(profileService.setCurrentProfile).toHaveBeenCalledWith(profile); + }); +}); diff --git a/web/client/src/app/app.component.ts b/web/client/src/app/app.component.ts new file mode 100644 index 0000000..102d006 --- /dev/null +++ b/web/client/src/app/app.component.ts @@ -0,0 +1,79 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { ProfileService } from './profile/profile.service'; +import { Profile } from './profile/profile'; +import { ServiceStatusService } from './service-status/service-status.service'; +import { NotificationsService } from './notifications/notifications.service'; +import { VersionInfo } from './types/versionInfo'; +import { MatDialog } from '@angular/material/dialog'; +import { setTheme } from 'ngx-bootstrap/utils'; +import { LogonInfo } from './types/logonInfo'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'], +}) +export class AppComponent implements OnInit { + + currentProfileName = ''; + serviceItemColumns = ['service', 'status']; + title = 'Iris Workstation'; + public username: string; + numNotifications = 0; + privs: any = {}; + data: VersionInfo = new VersionInfo(); + isSecure: boolean; + + constructor( + public router: Router, + public profileService: ProfileService, + public serviceStatusService: ServiceStatusService, + public notificationService: NotificationsService, + public logonInfo: LogonInfo, + public dialog: MatDialog, + ) { + // Manually set the bootstrap theme to bs4 + setTheme('bs4'); + this.profileService.username$ + .subscribe(name => { + this.username = name; + }); + + this.logonInfo.getIsSecure().subscribe(resp => { + this.isSecure = resp; + }) + + this.profileService.currentProfile$.subscribe((profile) => { + this.currentProfileName = profile.name; + }); + + this.profileService.getProfile() + .subscribe(prfs => { + this.currentProfileName = prfs.name; + }); + + this.profileService.getUserPrivileges().subscribe(privs => { + this.privs = privs; + }); + } + + ngOnInit() { + this.serviceStatusService.updateServiceVersions(); + } + + profileSelected(profile: Profile) { + this.profileService.setCurrentProfile(profile); + } + +} diff --git a/web/client/src/app/app.module.ts b/web/client/src/app/app.module.ts new file mode 100644 index 0000000..deceb42 --- /dev/null +++ b/web/client/src/app/app.module.ts @@ -0,0 +1,203 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { HttpClientModule } from '@angular/common/http'; +import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { AppRouting } from './app.routing'; +import { RouterModule } from '@angular/router'; +import { BrowserModule } from '@angular/platform-browser'; +import { Paper } from './utils/paper.service'; + +import { CreateComponent } from './create/create.component'; +import { HomeComponent } from './home/home.component'; +import { EbtsCreateService } from './create/ebts-create.service'; +import { ResponseReviewService } from './review/response-review.service'; +import { ReviewComponent } from './review/review.component'; +import { ReportDetailsComponent } from './review/report-details.component'; +import { CandidateContainerComponent, ViewCandidateDirective } from './review/candidate-container.component'; +import { UnrollCompareDialogComponent } from './review/unroll-compare/unroll-compare-dialog.component'; +import { UserComponent } from './user/user.component'; +import { ProfileService } from './profile/profile.service'; + +import { IrisDisplayModule } from './iris-display/iris-display.module'; +import { UtilsModule } from './utils/utils.module'; +import { DialogService } from './utils/dialog.serivce'; +import { AciiService } from './acii/acii.service'; +import { PersonService } from './person/person.service'; +import { FindIrisService } from './find-iris/find-iris.service'; +import { ManageComponent } from './manage/manage.component'; +import { NgxPaginationModule } from 'ngx-pagination'; +import { TabsModule } from 'ngx-bootstrap/tabs'; +import { PersonFilterPipe } from './manage/person-filter.pipe'; // <-- import the module +import { ErrorsHandler } from './error/errors-handler'; +import { ErrorHandler } from '@angular/core'; +import { HistoryComponent } from './history/history.component'; +import { HistoryService } from './history/history.service'; +import { SaveDialogComponent } from './save-dialog/save-dialog.component'; +import { TshepiiService } from './tshepii/tshepii.service'; +import { AdminComponent } from './admin/admin.component'; +import { AdminFilterPipe } from './admin/admin-filter.pipe'; +import { AdminService } from './admin/admin.service'; +import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; +import { ModalModule } from 'ngx-bootstrap/modal'; +import { TooltipModule } from 'ngx-bootstrap/tooltip'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { ToolManagerService } from './iris-display/tool-manager.service'; +import { BiqtContactService } from './biqt-contact/biqt-contact.service'; + +// Angular Material +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatSelectModule } from '@angular/material/select'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatButtonModule } from '@angular/material/button'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatCardModule } from '@angular/material/card'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatTableModule } from '@angular/material/table'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatInputModule } from '@angular/material/input'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatSliderModule } from '@angular/material/slider'; +import { MatIconModule } from '@angular/material/icon'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatNativeDateModule } from '@angular/material/core'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatSortModule } from '@angular/material/sort'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatSnackBarModule, MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatBadgeModule } from '@angular/material/badge'; + +import { DropzoneModule } from 'ngx-dropzone-wrapper'; +import { ReviewExportComponent } from './review-export/review-export.component'; +import { ReviewExportService } from './review-export/review-export.service'; +import { NotificationsComponent } from './notifications/notifications.component'; +import { ServiceStatusComponent } from './service-status/service-status.component'; +import { ServiceStatusService } from './service-status/service-status.service'; +import { CreateContentService } from './create/create.service'; +import { ReviewContentService } from './review/review.service'; +import { LogonInfo } from './types/logonInfo'; +import { DatePipe, HashLocationStrategy, LocationStrategy } from '@angular/common'; +import { NgChartsModule } from 'ng2-charts'; +import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; +import { PageNotFoundComponent } from './page-not-found/page-not-found.component'; +import {MatLegacyListModule} from '@angular/material/legacy-list'; +import { PupilDilationToolService } from './pupil-dilation-tool/pupil-dilation-tool.service'; +import { DualPDMService } from './dual-pdm/dual-pdm.service'; +import { PdmConfirmationDialogModule } from './pdm-confirmation-dialog/pdm-confirmation-dialog.module'; + + +@NgModule({ + imports: [ + BrowserModule, + RouterModule, + FormsModule, + HttpClientModule, + ReactiveFormsModule, + AppRouting, + UtilsModule, + IrisDisplayModule, + TabsModule.forRoot(), + NgxPaginationModule, + BsDropdownModule.forRoot(), + TooltipModule.forRoot(), + ModalModule.forRoot(), + BrowserAnimationsModule, + MatExpansionModule, + MatSelectModule, + MatToolbarModule, + MatButtonModule, + MatMenuModule, + MatCardModule, + MatTabsModule, + MatTableModule, + MatCheckboxModule, + MatInputModule, + MatPaginatorModule, + MatButtonToggleModule, + MatIconModule, + MatDividerModule, + MatSliderModule, + MatDatepickerModule, + MatNativeDateModule, + MatDialogModule, + MatSortModule, + MatTooltipModule, + MatChipsModule, + MatSnackBarModule, + DropzoneModule, + MatProgressSpinnerModule, + MatBadgeModule, + NgChartsModule, + MatLegacyListModule, + PdmConfirmationDialogModule + ], + declarations: [ + AppComponent, + HomeComponent, + UserComponent, + ReviewComponent, + CreateComponent, + CandidateContainerComponent, + ViewCandidateDirective, + UnrollCompareDialogComponent, + ReportDetailsComponent, + ServiceStatusComponent, + ManageComponent, + PersonFilterPipe, + HistoryComponent, + SaveDialogComponent, + AdminComponent, + AdminFilterPipe, + ReviewExportComponent, + NotificationsComponent, + ServiceStatusComponent, + PageNotFoundComponent + ], + providers: [ + {provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: {appearance: 'outline'}}, + {provide: ErrorHandler, useClass: ErrorsHandler}, + {provide: LocationStrategy, useClass: HashLocationStrategy}, + Paper, + DialogService, + ResponseReviewService, + ProfileService, + EbtsCreateService, + BiqtContactService, + AciiService, + TshepiiService, + DualPDMService, + EbtsCreateService, + HistoryService, + FindIrisService, + PersonService, + AdminService, + ReviewExportService, + CreateContentService, + ReviewContentService, + ServiceStatusService, + LogonInfo, + DatePipe, + ToolManagerService, + PupilDilationToolService, + {provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: {duration: 3000}} + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + bootstrap: [AppComponent] +}) + +export class AppModule { +} diff --git a/web/client/src/app/app.routing.ts b/web/client/src/app/app.routing.ts new file mode 100644 index 0000000..26863ad --- /dev/null +++ b/web/client/src/app/app.routing.ts @@ -0,0 +1,119 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable, NgModule } from '@angular/core'; +import { CanDeactivate, RouterModule, Routes, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; + +import { HomeComponent } from './home/home.component'; +import { CreateComponent } from './create/create.component'; +import { CreateResolver } from './create/create.resolver'; +import { ReviewComponent } from './review/review.component'; +import { ReviewResolver } from './review/review.resolver'; +import { UserComponent } from './user/user.component'; +import { Observable } from 'rxjs'; +import { ManageComponent } from './manage/manage.component'; +import { HistoryComponent } from './history/history.component'; +import { AdminComponent } from './admin/admin.component'; +import { ReviewExportComponent } from './review-export/review-export.component'; +import { PageNotFoundComponent } from './page-not-found/page-not-found.component'; + + +// based on https://angular.io/guide/router#candeactivate-handling-unsaved-changes +export interface CanComponentDeactivate { + canDeactivate: (url: string) => Observable | Promise | boolean; +} + +@Injectable() +export class CanDeactivateGuard implements CanDeactivate { + canDeactivate(component: CanComponentDeactivate, + currentRoute: ActivatedRouteSnapshot, + currentState: RouterStateSnapshot, + nextState: RouterStateSnapshot) { + return component.canDeactivate ? component.canDeactivate(nextState.url) : true; + } +} + +const appRoutes: Routes = [ + { + path: '', + redirectTo: 'home', + pathMatch: 'full' + }, + { + path: 'review/:id', + component: ReviewComponent, + resolve: { + config: ReviewResolver + }, + canDeactivate: [CanDeactivateGuard] + }, + { + path: 'review', + component: ReviewComponent, + resolve: { + config: ReviewResolver + }, + canDeactivate: [CanDeactivateGuard] + }, + { + path: 'export', + component: ReviewExportComponent, + canDeactivate: [CanDeactivateGuard] + }, + { + path: 'create/:id', + component: CreateComponent, + resolve: { + config: CreateResolver + } + }, + { + path: 'create', + component: CreateComponent, + resolve: { + config: CreateResolver + }, + canDeactivate: [CanDeactivateGuard] + }, + { + path: 'manage', + component: ManageComponent, + canDeactivate: [CanDeactivateGuard] + }, + { + path: 'user', + component: UserComponent + }, + { + path: 'home', + component: HomeComponent + }, + { + path: 'history', + component: HistoryComponent + }, + { + path: 'admin', + component: AdminComponent + }, + // otherwise redirect to home + {path: '**', + pathMatch: 'full', + component: PageNotFoundComponent} +]; + + +@NgModule({ + imports: [RouterModule.forRoot(appRoutes, {})], + exports: [RouterModule], + providers: [ReviewResolver, CreateResolver, CanDeactivateGuard], +}) +export class AppRouting { +} diff --git a/web/client/src/app/biqt-contact/biqt-contact.service.ts b/web/client/src/app/biqt-contact/biqt-contact.service.ts new file mode 100644 index 0000000..5a92d23 --- /dev/null +++ b/web/client/src/app/biqt-contact/biqt-contact.service.ts @@ -0,0 +1,184 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable, OnDestroy } from '@angular/core'; +import { Observable, timer, Subject, Subscription } from 'rxjs'; +import { switchMap, share, retry, takeUntil } from 'rxjs/operators'; +import { HttpClient } from '@angular/common/http'; +import { ServiceStatus } from '../types/service-response'; +import { BiqtResponse } from './biqtResponse'; +import { ContactClassifierResponse } from './contact-classifier-response'; + +@Injectable() +export class BiqtContactService implements OnDestroy { + public contactResponse$ = new Subject(); + public biqtResponse$ = new Subject(); + private biqtContactRequestStatus$: Observable; + private stopBiqtPolling$ = new Subject(); + private stopContactPolling$ = new Subject(); + private stopBiqtContactPolling$ = new Subject(); + private _biqtStatusSubscription: Subscription[] = []; + private _contactStatusSubscription: Subscription[] = []; + private biqtContactServiceUrl = '/api/service/biqtContact/'; + private biqtContactServiceStatusUrl = '/api/service/biqtContact/status/'; + private biqtContactSnapshotUrl = '/api/service/biqtContact/snapshot/'; + + constructor( + private http: HttpClient + ) { + } + + getBiqtContactStatus(ebtsImageId: string, modifiedImageId: string): Observable { + return this.http.get(this.biqtContactServiceStatusUrl + ebtsImageId + '/' + modifiedImageId); + } + + getBiqtResponse(imageId: string, isResubmit: boolean): Observable { + return this.http.get(this.biqtContactServiceUrl + 'biqt/' + imageId + '/' + isResubmit); + } + + getContactResponse(imageId: string, isResubmit: boolean): Observable { + return this.http.get(this.biqtContactServiceUrl + 'contact/' + imageId + '/' + isResubmit); + } + + getBiqtContactSnapshot(imageData: string, ebtsImageId: string): Observable { + return this.http.post(this.biqtContactSnapshotUrl + ebtsImageId, imageData); + } + + startBiqtContactPolling(ebtsImageId: string, modifiedImageId: string): void { + this.biqtContactRequestStatus$ = timer(1, 5000).pipe( //5000 = 5 second polling interval + switchMap(() => + this.getBiqtContactStatus(ebtsImageId, modifiedImageId) + ), + retry(4), + share(), + takeUntil(this.stopBiqtContactPolling$) + ); + } + + stopBiqtStatusPolling(imageId?: string): void { + this.stopBiqtPolling$.next(true); + this.stopBiqtPolling$.complete(); + if (imageId) { + this._biqtStatusSubscription[imageId].unsubscribe(); + } else { + this._biqtStatusSubscription.forEach(subscription => { + subscription.unsubscribe(); + }); + } + } + + stopContactStatusPolling(imageId?: string): void { + this.stopContactPolling$.next(true); + this.stopContactPolling$.complete(); + if (imageId) { + this._contactStatusSubscription[imageId].unsubscribe(); + } else { + this._contactStatusSubscription.forEach(subscription => { + subscription.unsubscribe(); + }); + } + } + + requestBiqtContactResponse(ebtsImageId: string, isResubmit: boolean, modifiedImageId?: string): void { + let imageId = ebtsImageId; + if (!!modifiedImageId) { + imageId = modifiedImageId; + } else { + modifiedImageId = '-1'; + } + this.startBiqtContactPolling(ebtsImageId, modifiedImageId); + let biqtIsReceived = this.requestBiqtResponse(imageId, isResubmit); + let contactIsReceived = this.requestContactResponse(imageId, isResubmit); + if (biqtIsReceived && contactIsReceived) { + this.stopBiqtContactPolling$.next(true); + this.stopBiqtContactPolling$.complete(); + } + } + + requestBiqtResponse(imageId: string, isResubmit: boolean): boolean { + let isReceived = false; + + this._biqtStatusSubscription[imageId] = this.biqtContactRequestStatus$.subscribe( + data => { + let status = data[0]; + switch (status) { + case ServiceStatus.Error: { + window.alert('BIQT error'); + break; + } + case ServiceStatus.Received: { + this.getBiqtResponse(imageId, isResubmit).subscribe( + biqtResponse => { + if (biqtResponse.status == ServiceStatus.Received) { + biqtResponse.isResubmit = isResubmit; + this.biqtResponse$.next(biqtResponse); + this.stopBiqtStatusPolling(imageId); + isReceived = true; + } + }); + break; + } + case ServiceStatus.Pending: { + break; + } + case ServiceStatus.NotSent: { + break; + } + default: + console.log('BIQT: No Status Yet'); + break; + } + }); + return isReceived; + } + + requestContactResponse(imageId: string, isResubmit: boolean): boolean { + let isReceived = false; + this._contactStatusSubscription[imageId] = this.biqtContactRequestStatus$.subscribe( + data => { + let status = data[1]; + switch (status) { + case ServiceStatus.Error: { + window.alert('Contact error'); + break; + } + case ServiceStatus.Received: { + this.getContactResponse(imageId, isResubmit).subscribe( + contactResponse => { + if (contactResponse.status == ServiceStatus.Received) { + contactResponse.isResubmit = isResubmit; + this.contactResponse$.next(contactResponse); + this.stopContactStatusPolling(imageId); + isReceived = true; + } + }); + break; + } + case ServiceStatus.Pending: { + break; + } + case ServiceStatus.NotSent: { + break; + } + default: + console.log('Contact: No Status Yet'); + break; + } + }); + return isReceived; + } + + ngOnDestroy() { + this.stopBiqtStatusPolling(); + this.stopContactStatusPolling(); + } + + +} diff --git a/web/client/src/app/biqt-contact/biqtResponse.ts b/web/client/src/app/biqt-contact/biqtResponse.ts new file mode 100644 index 0000000..3189eb4 --- /dev/null +++ b/web/client/src/app/biqt-contact/biqtResponse.ts @@ -0,0 +1,33 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { ServiceResponse, ServiceStatus } from '../types/service-response'; + +export class BiqtResponse extends ServiceResponse { + quality: number; + irisCenterX: number; + irisCenterY: number; + isResubmit: boolean; + + constructor( + quality: number, + irisCenterX: number, + irisCenterY: number, + imageId: string, + error: string, + status: ServiceStatus, + ebtsImageId: string, + isResubmit: boolean) { + super(imageId, error, status, ebtsImageId, isResubmit); + this.quality = quality; + this.irisCenterX = irisCenterX; + this.irisCenterY = irisCenterY; + } +} diff --git a/web/client/src/app/biqt-contact/contact-classifier-response.ts b/web/client/src/app/biqt-contact/contact-classifier-response.ts new file mode 100644 index 0000000..8f3b072 --- /dev/null +++ b/web/client/src/app/biqt-contact/contact-classifier-response.ts @@ -0,0 +1,30 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { ServiceResponse, ServiceStatus } from '../types/service-response'; + + +export class ContactClassifierResponse extends ServiceResponse { + detectionCode: string; + processTimeMS: string; + + constructor(detectionCode: string, + processTimeMS: string, + imageId: string, + error: string, + status: ServiceStatus, + ebtsImageId: string, + isResubmit: boolean + ) { + super(imageId, error, status, ebtsImageId, isResubmit); + this.detectionCode = detectionCode; + this.processTimeMS = processTimeMS; + } +} diff --git a/web/client/src/app/create/create.component.css b/web/client/src/app/create/create.component.css new file mode 100644 index 0000000..a272f04 --- /dev/null +++ b/web/client/src/app/create/create.component.css @@ -0,0 +1,42 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +#help { + text-align: center; +} + +#help > .guide { + display: inline-block; +} + +#help > .guide ~ .guide { + margin-left: 15px; +} + +.dropzone { + height: 100%; +} + +mat-form-field { + width: 100%; +} + +.mat-field-spaces { + height: 85px; +} + +.mb-2 { + margin-bottom: .75rem!important; +} + +::ng-deep .readonly-input .mat-mdc-text-field-wrapper { + background-color: #e9ecef; + opacity: 1; +} diff --git a/web/client/src/app/create/create.component.html b/web/client/src/app/create/create.component.html new file mode 100644 index 0000000..32784d4 --- /dev/null +++ b/web/client/src/app/create/create.component.html @@ -0,0 +1,290 @@ + +
+

Create an EBTS

+
+
+
+ +
+ + +
+
+
+
+
+
+
+

Upload Image

+ +
+
+ +
+ +
+
+ + + + +
+ +
+
+
+
+

+ Move - + Shift + Mouse Drag +

+

+ Zoom - + Mouse Wheel +

+
+
+
+ + +
+
+
+
+

Case Information

+ +
+ +
+ + + Image Quality + + + Check Quality + + + +
+ +
+ + + Case Prefix + Case Prefix is required and must be up to 24 alphanumeric characters. + + +
+ +
+ + + Case Identifier + Case Identifier is required and must be up to 24 alphanumeric characters. + + +
+ +
+ + + Case Extension + Count of case/pair search submissions + Case Extension must be a number. + +
+ +
+ + + Number of Candidates + The number must be between 20 and 50. + alphanumeric charcters + + +
+ +
+ + + Destination Agency Identifier + The destination agency identifier must be 9 + alphanumeric charcters. + + +
+ +
+ + + Originating Agency Identifier + The originating agency identifier must be 9 + alphanumeric charcters. + + +
+ +
+ + + Transaction Control Number + The transaction control number must be 10-40 + alphanumeric characters. + + +
+ +
+ + + Attention Indicator + A transaction must have an attention indicator and it must + not contain a '.'. + + +
+ +
+ + Eye Label + + {{eyeLabelEnum[eyeLabel]}} + + + + Auto Label + + +
+ +
+ + Contact Type + + {{contactTypeEnum[type]}} + + + +
+ +
+ + + Source Agency + A source agency must be 9 alphanumeric characters. + +
+ +
+ + + Iris Capture Date + + + The date must be a past or current date. + +
+ +
+ + + Rotation of Eye + *Counter-Clockwise is positive + +
+ +
+ + + Rotation of Eye Uncertainty + +
+
+ +
+
+
+
+ diff --git a/web/client/src/app/create/create.component.ts b/web/client/src/app/create/create.component.ts new file mode 100644 index 0000000..8dbf433 --- /dev/null +++ b/web/client/src/app/create/create.component.ts @@ -0,0 +1,448 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; +import { CreateIrisSearchRequest } from '../types/create-iris-search-request'; +import { EyeLabel } from '../types/eye-label'; +import { EbtsCreateService } from './ebts-create.service'; +import { ProfileService } from '../profile/profile.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ContentService } from '../utils/content.service'; +import { CreateContentService } from './create.service'; +import { IrisDisplayComponent } from '../iris-display/iris-display.component'; +import { Profile } from '../profile/profile'; +import { NgForm } from '@angular/forms'; +import { ToolManagerService } from '../iris-display/tool-manager.service'; +import { HistoryService } from '../history/history.service'; +import { SaveDialogComponent } from '../save-dialog/save-dialog.component'; +import { DialogService } from '../utils/dialog.serivce'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { ContactType } from '../types/contact-type'; +import { BiqtContactService } from '../biqt-contact/biqt-contact.service'; + +@Component({ + templateUrl: 'create.component.html', + styleUrls: ['./create.component.css'], + providers: [ + {provide: ContentService, useClass: CreateContentService}, + ToolManagerService + ] +}) + +export class CreateComponent implements OnInit, OnDestroy { + config: any; + configFileUrl = './iris_create_config.json'; + createUploadUrl = '/api/create/upload'; + eyeLabelValues = Object.keys(EyeLabel).map(k => EyeLabel[k]).filter(v => typeof v == 'number'); //the value of the Eye Label select is a number + eyeLabelEnum = EyeLabel; //needed for the Eye Label select element + contactTypeValues = Object.keys(ContactType).map(k => ContactType[k]).filter(v => typeof v == 'number'); + contactTypeEnum = ContactType; + + _hasImageSelected = false; + set hasImageSelected(s: boolean) { + this._hasImageSelected = s; + } + + get hasImageSelected(): boolean { + return this._hasImageSelected; + } + + loadedFromSave = false; + image: HTMLImageElement; + saved = new Subject(); + + @ViewChild('probeDisplay') probeDisplay: IrisDisplayComponent; + @ViewChild('createForm', {static: true}) createForm: NgForm; + @ViewChild('wholeForm', {static: true}) wholeForm: NgForm; + @ViewChild('saveDialog', {static: true}) saveDialog: SaveDialogComponent; + @ViewChild('irisCaptureDate', {static: true}) captureDate: ElementRef; + + errorMessages: string[]; + irisImageQuality = -1; + canvasReset = false; + saveInProgress = false; + originalQuality = -1; + redirectUrl = ''; + createIrisSearchRequest: CreateIrisSearchRequest = new CreateIrisSearchRequest(); + + irisCaptDate: Date = new Date(); + initTcn: string = this.generateTcn(); + + showOriginal: BehaviorSubject; + showUnrolled: BehaviorSubject; + _biqtResponseSubscription: Subscription; + _contactResponseSubscription: Subscription; + _aciiResponseSubscription: Subscription; + okayToNavigate = false; + userParentGroupId: string; + + constructor( + private route: ActivatedRoute, + private contentSvc: ContentService, + private profileService: ProfileService, + private irisService: EbtsCreateService, + private biqtContactService: BiqtContactService, + private historyService: HistoryService, + private dialogSvc: DialogService, + public createService: CreateContentService, + private router: Router, + private matSnackBar: MatSnackBar + ) { + } + + ngOnInit() { + this.image = new Image(); + this.image.crossOrigin = 'anonymous'; + + // Route Resolver for config file + this.route.data.subscribe(data => { + this.config = data.config; + }); + + this.contentSvc.configure(this.config); + this.config = Object.assign(this.config, this.contentSvc.config); + + this.createIrisSearchRequest.message.numberOfCandidates = this.config.num_of_candidates_default; + + this.createIrisSearchRequest.message.transactionControlNumber = this.generateTcn(); + + this.setIrisCaptureDate(this.irisCaptDate) + + //set the fields from the profile + this.setProfileFields(this.profileService.getCurrentProfile()); + this.profileService.currentProfile$.subscribe((profile) => { + this.setProfileFields(profile); + }); + + this.saveDialog.closed$.subscribe((closed) => { + if (closed) { + if (this.saveDialog.continue) { + if (this.saveDialog.save) { + // Save Off Search + this.finishSearchRequest(); + this.createIrisSearchRequest.message.imageData.imageData = this.probeDisplay.exportFlattenedPNG(true); + this.historyService.saveIrisSearchRequest(this.createIrisSearchRequest) + .subscribe((response) => { + this.saved.next(this.createIrisSearchRequest.message.imageData.annotations); + setTimeout(() => { + this.matSnackBar.open('EBTS saved', 'Dismiss'); + }, 1000); + this.okayToNavigate = true; + this.router.navigateByUrl(this.redirectUrl); + }); + } else { + this.okayToNavigate = true; + this.router.navigateByUrl(this.redirectUrl); + } + } + this.saveDialog.hide(); + } + }); + + if (this.historyService.hasIrisSearchRequest) { + this.createIrisSearchRequest = this.historyService.savedIrisSearchRequest; + this.irisCaptDate = new Date(this.createIrisSearchRequest.message.irisCaptureDate); + // Saving the userParentGroupId to set the toolManager's userParentGroupId later + this.historyService.savedIrisSearchRequest.message.imageData.annotations.some(annotation => { + if (annotation.type == 15) { + this.userParentGroupId = annotation.id; + } + }); + + if (!this.createIrisSearchRequest.message.transactionControlNumber) { + this.createIrisSearchRequest.message.transactionControlNumber = this.generateTcn(); + } + + + this.image.onload = () => { + this.hasImageSelected = true; + this.loadedFromSave = true; + }; + + //get the original id of the image + this.image.src = `${this.config.imageURL}${this.createIrisSearchRequest.message.imageData.ebtsImageId}`; + } else { + this.createService.irisImageQuality = this.createService.initIrisImageQuality; + } + + this.showOriginal = new BehaviorSubject(true); + this.showUnrolled = new BehaviorSubject(true); + } + + + ngOnDestroy() { + this.historyService.clearIrisSearchRequest(); + if (this._biqtResponseSubscription) { + this._biqtResponseSubscription.unsubscribe(); + } + if (this._contactResponseSubscription) { + this._contactResponseSubscription.unsubscribe(); + } + if (this._aciiResponseSubscription) { + this._aciiResponseSubscription.unsubscribe(); + } + } + + setIrisCaptureDate($event) { + if ($event != null) { + this.createIrisSearchRequest.message.irisCaptureDate = this.getDateString($event) + } + } + + changeImage() { + if (this.dialogSvc.confirmBooleam('This will discard the current image. Proceed?')) { + this.image = new Image(); + this.image.crossOrigin = 'anonymous'; + this.hasImageSelected = false; + } + } + + changeCaseInformation() { + if (this.dialogSvc.confirmBooleam('This will remove all case information. Proceed?')) { + this.createForm.resetForm({ + imageQualityInput: this.createService.initIrisImageQuality, + caseExtension: this.createService.initExtension, + numberOfCandidates: this.createService.initNumCandidate, + destinationAgencyIdentifier: this.createService.initDai, + originatingAgencyIdentifier: this.createService.initOri, + transactionControlNumber: this.initTcn, + attentionIndicator: this.createService.initAtt, + eyeLabel: this.createService.initEyeLabel, + contactType: this.createService.initContactType, + sourceAgency: this.createService.initSourceAgency, + irisCaptureDate: this.irisCaptDate, + rotationOfEye: this.createService.initRoe, + rotationUncertainty: this.createService.initRou + }); + } + } + + canDeactivate(url: string) { + if (this.okayToNavigate) { + return true; + } else if (this.isComponentDirty()) { + this.redirectUrl = url; + this.saveDialog.show(); + return false; + } else { + return true; + } + } + + isComponentDirty() { + if (this.hasImageSelected) { + if (this.createForm.dirty) { + return true; + } else { + // Checks if any annotations were made and if the save button wasn't hit + return this.probeDisplay.getIrisModified() && !this.probeDisplay.toolManager.hasSaveButtonSelected; + } + } + return false; + } + + private setProfileFields(profile: Profile) { + this.createIrisSearchRequest.message.destinationAgencyIdentifier = profile.destinationAgencyIdentifier; + this.createIrisSearchRequest.message.originatingAgencyIdentifier = profile.originatingAgencyIdentifier; + this.createIrisSearchRequest.message.attentionIndicator = profile.attentionIndicator; + this.createIrisSearchRequest.message.sourceAgency = profile.sourceAgency; + } + + generateTcn(): string { + const d = new Date(); + const hour = new Intl.NumberFormat('en-us', {minimumIntegerDigits: 2}).format(d.getHours()); + const minute = new Intl.NumberFormat('en-us', {minimumIntegerDigits: 2}).format(d.getMinutes()); + const second = new Intl.NumberFormat('en-us', {minimumIntegerDigits: 2}).format(d.getSeconds()); + const datestring = this.getDateString(d) + hour + minute + second; + + return 'IWP-' + + datestring + '-' + + Math.floor(Math.random() * 1000000); + } + + handleFileSuccess($event) { + this.createIrisSearchRequest = $event[1]; + + // If user edited the information before loading in the image, use the inputted information. + // Doesn't include iris image quality, eye label, contact type, and rotation of eye of the message property because those update with the image + // Doesn't include captureIrisDate since it's handled by a different method + let formControls = this.createForm.controls; + + this.createIrisSearchRequest.message.cinPrefix = formControls.cinPrefix.value; + this.createIrisSearchRequest.message.cinIdentifier = formControls.cinIdentifier.value; + this.createIrisSearchRequest.message.caseExtension = formControls.caseExtension.value; + this.createIrisSearchRequest.message.numberOfCandidates = formControls.numberOfCandidates.value; + this.createIrisSearchRequest.message.destinationAgencyIdentifier = formControls.destinationAgencyIdentifier.value; + this.createIrisSearchRequest.message.originatingAgencyIdentifier = formControls.originatingAgencyIdentifier.value; + this.createIrisSearchRequest.message.transactionControlNumber = formControls.transactionControlNumber.value; + this.createIrisSearchRequest.message.attentionIndicator = formControls.attentionIndicator.value; + this.createIrisSearchRequest.message.sourceAgency = formControls.sourceAgency.value; + this.createIrisSearchRequest.message.rotationOfEye = formControls.rotationOfEye.value; + this.createIrisSearchRequest.message.rotationUncertainty = formControls.rotationUncertainty.value; + + if (this.createIrisSearchRequest.message.errorMessages.length > 0) { + console.log(this.createIrisSearchRequest.message.errorMessages); + } + if (this.createIrisSearchRequest.message.errorMessages.length === 0) { + this.image.onload = () => { + this.hasImageSelected = true; + }; + + this.image.src = `${this.config.imageURL}${this.createIrisSearchRequest.message.imageData.ebtsImageId}`; + } else { + this.errorMessages = []; //clear the error messages + this.errorMessages = this.createIrisSearchRequest.message.errorMessages; + } + } + + checkBiqtSnapshot(){ + let imageData = this.probeDisplay.exportFlattenedPNG(false); + this.biqtContactService.getBiqtContactSnapshot(imageData, this.probeDisplay.currentImageData.ebtsImageId).subscribe((imageId) =>{ + let modifiedImageId = imageId; + if(modifiedImageId != "-1"){ + this.createService.getIrisImageQualityAndContact(this.probeDisplay.currentImageData.ebtsImageId, false, modifiedImageId); + } + }) + } + + irisDisplayInitialized(evt) { + if (this.canvasReset) { + this.canvasReset = false; + this.irisImageQuality = this.originalQuality; + this.createIrisSearchRequest.message.rotationOfEye = Math.round(this.probeDisplay.toolManager._project.view.rotation); + } else if (this.probeDisplay) { + if (!this.historyService.hasIrisSearchRequest) { + this.createService.getIrisImageQualityAndContact(this.createIrisSearchRequest.message.imageData.ebtsImageId, false); + if (this.originalQuality == -1) { + this.originalQuality = this.createService.irisImageQuality; + } + this.createService.getAutoEyeLabel(this.createIrisSearchRequest.message.imageData.ebtsImageId, false); + } else { + this.createService.irisImageQuality = this.createIrisSearchRequest.message.imageData.eyeLabel; + this.createService.eyeLabel = this.createIrisSearchRequest.message.imageData.eyeLabel; + } + } + + this.probeDisplay.toolChange$.subscribe((toolName) => { + this.createIrisSearchRequest.message.rotationOfEye = Math.round(this.probeDisplay.toolManager._project.view.rotation); + }); + + // If the create report is coming from a save, + if (this.historyService.hasIrisSearchRequest) { + this.probeDisplay.toolManager.userParentGroupId = this.userParentGroupId; + } + } + + handleFileFailure(obj) { + console.log('Error getting image file'); + } + + setEyeLabel(eyeLabel: EyeLabel): void { + this.createIrisSearchRequest.message.imageData.eyeLabel = eyeLabel; + } + + resetIris($event) { + const imgUri = this.image.src; + + this.hasImageSelected = false; + this.image.onload = () => { + this.hasImageSelected = true; + }; + this.image.src = imgUri; + + this.canvasReset = true; + } + + getDateString(d: Date) { + const year = new Intl.NumberFormat('en-us', {minimumIntegerDigits: 4, useGrouping: false}).format(d.getFullYear()); + const month = new Intl.NumberFormat('en-us', {minimumIntegerDigits: 2}).format(d.getMonth() + 1); + const day = new Intl.NumberFormat('en-us', {minimumIntegerDigits: 2}).format(d.getDate()); + const dateString = year + month + day; + return dateString; + } + + finishSearchRequest() { + this.saveInProgress = true; + this.createIrisSearchRequest.message.imageData.annotations = this.probeDisplay.exportAnnotations(); + this.createIrisSearchRequest.message.imageData.eyeLabel = this.createService.eyeLabel; + this.createIrisSearchRequest.message.imageData.contactType = this.createService.contactType; + } + + submitIrisSearch(evt) { + var _saveSubscription = this.saveSearchRequest().subscribe((response) => { + if (response) { + this.createIrisSearchRequest.isFinished = true; + + //save the image with all the annotations added to it + this.createIrisSearchRequest.message.imageData.imageData = this.probeDisplay.exportFlattenedPNG(true); + const handleResponse = (ebtsFile) => { //data is a Blob object + //create the link element + const a = document.createElement('a'); + document.body.appendChild(a); + + //set the fields + const objectUrl = URL.createObjectURL(ebtsFile); + a.href = objectUrl; + a.download = this.createIrisSearchRequest.message.transactionControlNumber + '.ebts'; + a.click(); + window.URL.revokeObjectURL(objectUrl); + + setTimeout(() => { + this.matSnackBar.open('Generated EBTS', 'Dismiss'); + window.location.reload(); + }, 1000); + }; + + // Regardless if it's from a case, it will ither create the search request and download it, or immediately download it + if (!this.historyService.hasIrisSearchRequest || this.historyService.savedIrisSearchRequest.filePath == null) { + this.irisService.createEbts(this.createIrisSearchRequest) + .subscribe(handleResponse); + } else { + const req = this.historyService.savedIrisSearchRequest; + req.message = this.createIrisSearchRequest.message; + req.message.invalidFields = []; + this.irisService.downloadEBTS(req.id) + .subscribe(handleResponse); + } + this.saveInProgress = false; + _saveSubscription.unsubscribe(); + } + }); + } + + saveSearchRequest(): Observable { + var submitResponseSubject = new Subject(); + submitResponseSubject.next(false); + this.saveInProgress = true; + this.finishSearchRequest(); + this.createIrisSearchRequest.message.imageData.imageData = this.probeDisplay.exportFlattenedPNG(true); + + //update the current case with the EbtsSearch + const updateCase = (searchId) => { + + this.createForm.control.markAsPristine(); + this.saveInProgress = false; + }; + + this.historyService.saveIrisSearchRequest(this.createIrisSearchRequest) + .subscribe((response) => { + this.saved.next(this.createIrisSearchRequest.message.imageData.annotations); + updateCase(response.message.id); + setTimeout(() => { + this.matSnackBar.open('EBTS saved', 'Dismiss'); + }, 1000); + submitResponseSubject.next(true) + }); + + this.probeDisplay.toolManager.hasSaveButtonSelected = true; + return submitResponseSubject + } +} diff --git a/web/client/src/app/create/create.resolver.ts b/web/client/src/app/create/create.resolver.ts new file mode 100644 index 0000000..db8eeec --- /dev/null +++ b/web/client/src/app/create/create.resolver.ts @@ -0,0 +1,33 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; + + +import { ProfileService } from '../profile/profile.service'; +import { HttpClient } from '@angular/common/http'; + +@Injectable() +export class CreateResolver implements Resolve> { + + configFileUrl = './iris_create_config.json'; + + constructor(private http: HttpClient, private profileService: ProfileService) { + } + + resolve(route: ActivatedRouteSnapshot, + state: RouterStateSnapshot): Promise { + this.profileService.getProfile().subscribe(prfs => { + // Do nothing. simply a session alive check + }); + return this.http.get(this.configFileUrl).toPromise().then(r => r); + } +} diff --git a/web/client/src/app/create/create.service.ts b/web/client/src/app/create/create.service.ts new file mode 100644 index 0000000..b0074be --- /dev/null +++ b/web/client/src/app/create/create.service.ts @@ -0,0 +1,182 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; + + +import { ContentService } from '../utils/content.service'; +import { EyeLabel } from '../types/eye-label'; +import { AciiService } from '../acii/acii.service'; +import { Subscription } from 'rxjs'; +import { ContactType } from '../types/contact-type'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { ServiceStatusNameType } from '../types/service-status'; +import { ServiceStatus } from '../types/service-response'; +import { BiqtContactService } from '../biqt-contact/biqt-contact.service'; + + +@Injectable() +export class CreateContentService extends ContentService { + protected _config: object = { + imageURL: '/api/image/', + url: '/api/iris/create', + acceptedFiles: 'image/png, image/jp2, image/jp2l, image/bmp, image/jpeg, image/jpg, image/tiff, .tiff', + maxFiles: 1, + headers: {'X-XSRF-TOKEN': document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*\=\s*([^;]*).*$)|^.*$/, '$1')} + }; + configFileUrl = './iris_create_config.json'; + colors: any[] = []; + irisImageQuality = -1; + eyeLabel: EyeLabel = EyeLabel['']; + contactType: ContactType = ContactType['']; + + initIrisImageQuality = -1; + initExtension = 0; + initNumCandidate = 50; + initDai = 'TempDAI12'; + initOri = 'TempORI12'; + initAtt = 'Attention'; + initEyeLabel: EyeLabel = EyeLabel['']; + initContactType: ContactType = ContactType['']; + initSourceAgency = 'SRCAGENCY'; + initRoe = 0; + initRou = 0; + + + _biqtResponseSubscription: Subscription; + _contactResponseSubscription: Subscription; + _aciiResponseSubscription: Subscription; + + constructor( + private aciiService: AciiService, + private biqtContactService: BiqtContactService, + private matSnackBar: MatSnackBar, + ) { + super(); + } + + getAutoEyeLabel(ebtsImageId: string, isResubmit: boolean, modifiedImageId?: string): void { + this.aciiService.requestAciiResponse(ebtsImageId, isResubmit, modifiedImageId); + const pendingTimer = this.setPendingTimer(isResubmit, ServiceStatusNameType.acii); + this._aciiResponseSubscription = this.aciiService.aciiResponse$.subscribe( + aciiResponse => { + this.clearPendingTimer(aciiResponse.status, pendingTimer); + const orientation = aciiResponse.horizontal; + this.setAutoEyeLabel(orientation); + this.showSnackBar(aciiResponse.isResubmit, false, ServiceStatusNameType.acii); + this._aciiResponseSubscription.unsubscribe(); + } + ); + } + + setAutoEyeLabel(orientation): void { + let response; + switch (orientation) { + case 'Undefined': + case '0': + response = EyeLabel.Undefined; + break; + case 'Right': + case '1': + response = EyeLabel.Right; + break; + case 'Left': + case '2': + response = EyeLabel.Left; + break; + case '': + case '3': + default: + response = EyeLabel['']; + break; + } + this.eyeLabel = response; + } + + getIrisImageQualityAndContact(ebtsImageId: string, isResubmit: boolean, modifiedImageId?: string): void { + this.biqtContactService.requestBiqtContactResponse(ebtsImageId, isResubmit, modifiedImageId); + const biqtPendingTimer = this.setPendingTimer(isResubmit, ServiceStatusNameType.biqt); + const contactPendingTimer = this.setPendingTimer(isResubmit, ServiceStatusNameType.contactdetection); + this._biqtResponseSubscription = this.biqtContactService.biqtResponse$.subscribe( + response => { + this.clearPendingTimer(response.status, biqtPendingTimer); + if (response.quality >= 0) { + this.irisImageQuality = response.quality; + this.showSnackBar(response.isResubmit, false, ServiceStatusNameType.biqt); + } + + this._biqtResponseSubscription.unsubscribe(); + }); + + this._contactResponseSubscription = this.biqtContactService.contactResponse$.subscribe( + response => { + this.clearPendingTimer(response.status, contactPendingTimer); + this.contactType = this.getContactType(response.detectionCode) + this.showSnackBar(response.isResubmit, false, ServiceStatusNameType.contactdetection); + this._contactResponseSubscription.unsubscribe(); + + } + ) + } + + setPendingTimer(isResubmit: boolean, serviceName: ServiceStatusNameType): any { + let timeInterval = 8000; + // Needs to set each service to a different timer so they don't overlap + switch(serviceName) { + case ServiceStatusNameType.acii: { + break; + } + case ServiceStatusNameType.biqt: { + timeInterval += 500; + break; + } + case ServiceStatusNameType.contactdetection: { + timeInterval += 800; + break; + } + case ServiceStatusNameType.tshepii: { + timeInterval += 1100; + break; + } + } + + const timer = setInterval(() => this.showSnackBar(isResubmit, true, serviceName), timeInterval); + return timer; + } + + clearPendingTimer(responseStatus: ServiceStatus, pendingTimer: number): void { + if (responseStatus == ServiceStatus.Received) { + clearInterval(pendingTimer); + } + } + + showSnackBar(isResubmit: boolean, isPendingStill: boolean, serviceName: ServiceStatusNameType, customMessage?: string) { + if (!!customMessage) { + this.matSnackBar.open(customMessage, 'Dismiss'); + } + if (isResubmit && !isPendingStill) { + this.matSnackBar.open('Resubmitting image finished for ' + serviceName, 'Dismiss'); + } + } + + getContactType(contactType: string): ContactType { + switch (contactType) { + case '0': + return ContactType.None; + case '1': + return ContactType.Cosmetic; + case '2': + return ContactType.Clear; + case '3': + default: + return ContactType['']; + } + } +} diff --git a/web/client/src/app/create/ebts-create.service.spec.ts b/web/client/src/app/create/ebts-create.service.spec.ts new file mode 100644 index 0000000..7f5a4aa --- /dev/null +++ b/web/client/src/app/create/ebts-create.service.spec.ts @@ -0,0 +1,59 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { TestBed, inject } from '@angular/core/testing'; +import { HttpClientModule, HttpClient } from '@angular/common/http'; +import { HttpTestingController } from '@angular/common/http/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; + +import { EbtsCreateService } from './ebts-create.service'; +import { CreateIrisSearchRequest } from '../types/create-iris-search-request'; +import { EbtsMessageRequest } from '../types/ebts-message-request'; + +describe('XI. The EbtsCreateService class:', () => { + const searchRequest: CreateIrisSearchRequest = { + id:'12345', + message : new EbtsMessageRequest(), + userId : '67890', + creationDateEpoch : 0, + isFinished : false, + filePath: '' + }; + const expectedResponse: Blob = new Blob(); + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [EbtsCreateService], + imports: [HttpClientModule, HttpClientTestingModule], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + }); + }); + it('a. createEbts takes an object instance of the CreateIrisSearchRequest class and returns an Observable Blob.', inject( [EbtsCreateService, HttpClient, HttpTestingController], ( service: EbtsCreateService, http: HttpClient, httpMock: HttpTestingController ) => { + service.createEbts(searchRequest) + .subscribe( (resp: any) => { + expect(resp).toBe(expectedResponse); + }); + + const search = httpMock.expectOne(service.createServiceUrl); + search.flush(expectedResponse); + })); + + it('b. downloadEBTS takes a searchId and returns an Observable Blob.', inject( [EbtsCreateService, HttpClient, HttpTestingController], ( service: EbtsCreateService, http: HttpClient, httpMock: HttpTestingController ) => { + const searchId = '12345'; + service.downloadEBTS(searchId) + .subscribe( (resp: any) => { + expect(resp).toBe(expectedResponse); + }); + + const search = httpMock.expectOne(service.ebtsServiceUrl+searchId); + search.flush(expectedResponse); + })); +}); diff --git a/web/client/src/app/create/ebts-create.service.ts b/web/client/src/app/create/ebts-create.service.ts new file mode 100644 index 0000000..c4517d2 --- /dev/null +++ b/web/client/src/app/create/ebts-create.service.ts @@ -0,0 +1,40 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { CreateIrisSearchRequest } from '../types/create-iris-search-request'; + +@Injectable() +export class EbtsCreateService { + + createServiceUrl = '/api/iris/search'; + ebtsServiceUrl = '/api/iris/search/download/'; + + constructor( + private http: HttpClient + ) { } + + createEbts( searchRequest: CreateIrisSearchRequest ): Observable { + //also calling this post doesn't work either for some reason + return this.http.post(this.createServiceUrl, searchRequest, { + headers: new HttpHeaders({ 'Content-Type' : 'application/json' }), + responseType: 'blob' //this is the dumb but required by typescript + }); + } + + downloadEBTS( searchId: string ): Observable { + return this.http.get(this.ebtsServiceUrl+searchId, { + responseType: 'blob' + }); + } + +} diff --git a/web/client/src/app/dual-pdm/dual-pdm-compare-response.ts b/web/client/src/app/dual-pdm/dual-pdm-compare-response.ts new file mode 100644 index 0000000..90cc68f --- /dev/null +++ b/web/client/src/app/dual-pdm/dual-pdm-compare-response.ts @@ -0,0 +1,28 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { ServiceStatus } from "../types/service-response"; + +export interface DualPDMCompareResponse { + probe: Uint8Array; + candidate: Uint8Array; + status: ServiceStatus; +} + +// Consuming Endpoint: /api/service/dualpdm/response/{imageId} in DualPDMResponse.java +export class DualDeformedImage { + imageBytes: number[]; + + constructor( + imageBytes: number[], + ) { + this.imageBytes = imageBytes; + } +} diff --git a/web/client/src/app/dual-pdm/dual-pdm.component.css b/web/client/src/app/dual-pdm/dual-pdm.component.css new file mode 100644 index 0000000..614fe78 --- /dev/null +++ b/web/client/src/app/dual-pdm/dual-pdm.component.css @@ -0,0 +1,14 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +.cancel-button { + background-color: rgb(112, 112, 112); + color: white; +} \ No newline at end of file diff --git a/web/client/src/app/dual-pdm/dual-pdm.component.html b/web/client/src/app/dual-pdm/dual-pdm.component.html new file mode 100644 index 0000000..3c1b707 --- /dev/null +++ b/web/client/src/app/dual-pdm/dual-pdm.component.html @@ -0,0 +1,15 @@ + + +
+ Set Dual PDM Image + + +
diff --git a/web/client/src/app/dual-pdm/dual-pdm.components.ts b/web/client/src/app/dual-pdm/dual-pdm.components.ts new file mode 100644 index 0000000..9e79761 --- /dev/null +++ b/web/client/src/app/dual-pdm/dual-pdm.components.ts @@ -0,0 +1,45 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ +import { Component, OnInit } from '@angular/core'; +import { IrisDisplayComponent } from '../iris-display/iris-display.component'; +import { AnnotationType } from '../types/annotation'; +import { ToolManagerService } from '../iris-display/tool-manager.service'; +import { ReviewContentService } from '../review/review.service'; + +@Component({ + selector: 'app-dual-pdm', + templateUrl: './dual-pdm.component.html', + styleUrls: ['./dual-pdm.component.css'] +}) +export class DualPDMComponent implements OnInit { + + static icon = 'all_out'; + static id = 'Dual Pupil Dilation Comparison'; + static type: AnnotationType = AnnotationType.DUAL_PDM; + static iconType = "material"; + + onSetButtonClicked = false; + + constructor(private irisDisplayComponent: IrisDisplayComponent, private toolManager: ToolManagerService, public reviewService: ReviewContentService) {} + + ngOnInit(): void { + // Initialize component logic if needed + } + + onSet() { + this.irisDisplayComponent.getPdmResponses(DualPDMComponent.id, DualPDMComponent.type, DualPDMComponent.icon, DualPDMComponent.iconType, true); + this.onSetButtonClicked = true; + } + + onUnset() { + this.toolManager.removeItem(this.irisDisplayComponent.pdmGroupAnnotation.id, false, false, false); + this.onSetButtonClicked = false; + } +} diff --git a/web/client/src/app/dual-pdm/dual-pdm.service.ts b/web/client/src/app/dual-pdm/dual-pdm.service.ts new file mode 100644 index 0000000..92c0c1a --- /dev/null +++ b/web/client/src/app/dual-pdm/dual-pdm.service.ts @@ -0,0 +1,127 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ +import { Injectable, OnDestroy } from '@angular/core'; +import { Observable, timer, Subject, Subscription } from 'rxjs'; +import { switchMap, share, retry, takeUntil } from 'rxjs/operators'; +import { HttpClient } from '@angular/common/http'; +import { ServiceStatus } from '../types/service-response'; +import { DualPDMCompareResponse, DualDeformedImage } from './dual-pdm-compare-response'; +import { Annotation } from '../types/annotation'; + + +@Injectable() +export class DualPDMService implements OnDestroy { + private dualPDMURL = 'api/service/dualpdm/'; + private stopResponsePolling$ = new Subject(); + private stopComparePolling$ = new Subject(); + public dualpdmResponseStatus$: Observable + public dualPDMCompareStatus$: Observable + _dualPDMCompareSubscription: Subscription[] = []; + public dualPdmGroupAnnotation: Annotation = undefined; + + constructor( + private http: HttpClient + ) {} + + getDualPdmResponseStatus(probeEbtsImageId: string, candidateEbtsImageId: string, probeModifiedImageId: string = '-1', candidateModifiedImageId: string = '-1'): Observable { + return this.http.get(this.dualPDMURL + 'status/' + probeEbtsImageId + '/' + candidateEbtsImageId + '/' + probeModifiedImageId + '/' + candidateModifiedImageId); + } + + // Polls until both Probe and Candidate images are saved in DualPDMResponseRepository + startDualPdmResponseStatusPolling(probeEbtsImageId: string, candidateEbtsImageId: string, probeModifiedImageId?: string, candidateModifiedImageId?: string): void { + this.dualpdmResponseStatus$ = timer(1, 5000).pipe( + switchMap(() => + this.getDualPdmResponseStatus(probeEbtsImageId, candidateEbtsImageId, probeModifiedImageId, candidateModifiedImageId) + ), + retry(4), + share(), + takeUntil(this.stopResponsePolling$) + ); + } + + stopDualPDMResponseStatusPolling(): void{ + this.stopResponsePolling$.next(true); + this.stopResponsePolling$.complete(); + } + + // Retreives the combination of Probe and Candidate Images + getDualPDMComparison(probeId: string, candidateId: string, isResubmit: boolean): Observable { + return this.http.get(this.dualPDMURL + 'compare/' + probeId + '/' + candidateId + '/' + isResubmit); + } + + getDualPdmCompareResponseStatus(probeEbtsImageId: string, candidateEbtsImageId: string, probeModifiedImageId: string = '-1', candidateModifiedImageId: string = '-1'): Observable { + return this.http.get(this.dualPDMURL + 'compare/status/' + probeEbtsImageId + '/' + candidateEbtsImageId + '/' + probeModifiedImageId + '/' + candidateModifiedImageId); + } + + // Polls until the combination of Probe and Candidate images are saved in DualPDMCompareResponseRepository + startDualPDMCompareStatusPolling(probeEbtsImageId: string, candidateEbtsImageId: string, probeModifiedImageId?: string, candidateModifiedImageId?: string): void { + this.dualPDMCompareStatus$ = timer(1, 5000).pipe( + switchMap(() => + this.getDualPdmCompareResponseStatus(probeEbtsImageId, candidateEbtsImageId, probeModifiedImageId, candidateModifiedImageId) + ), + retry(4), + share(), + takeUntil(this.stopComparePolling$) + ); + } + + stopDualPDMCompareStatusPolling(imageId?: string): void { + if (imageId){ + this._dualPDMCompareSubscription[imageId].unsubscribe(); + } else { + this.stopComparePolling$.next(true); + this.stopComparePolling$.complete(); + this._dualPDMCompareSubscription.forEach(subscription => { + subscription.unsubscribe(); + }); + } + } + + // Retreives projected image (i.e. Probe ONTO Candidate, Candidate ONTO Probe) + getDualPDMResponseImage(imageId: string): Observable { + return this.http.get('/api/service/dualpdm/response/' + imageId); + } + + // Prepares to display byte array through HTML canvas and image elements + async prepImage(dualPDMResponseImage: DualDeformedImage): Promise<{dualDeformedImage: DualDeformedImage, canvas: HTMLCanvasElement, img: HTMLImageElement}> { + var paddedImageData = dualPDMResponseImage.imageBytes; + var imageData; + + const imageArray = new Uint8Array(paddedImageData); + const blob = new Blob([imageArray], { type: 'image/png' }); + + const img = await new Promise((resolve, reject) => { + const image = new Image(); + image.onload = () => resolve(image); + image.onerror = (err) => reject(err); + image.src = URL.createObjectURL(blob); + }); + + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext('2d'); + + if (!ctx) { + throw new Error("Can't get 2D context"); + } + ctx.drawImage(img, 0, 0); + + // Get raw pixel data (RGBA values) + imageData = ctx.getImageData(0, 0, img.width, img.height); + + var dualDeformedImage = new DualDeformedImage(imageData); + return {dualDeformedImage, canvas, img}; + } + + ngOnDestroy() { + this.stopDualPDMResponseStatusPolling(); + } +} \ No newline at end of file diff --git a/web/client/src/app/error/errors-handler.ts b/web/client/src/app/error/errors-handler.ts new file mode 100644 index 0000000..f83970d --- /dev/null +++ b/web/client/src/app/error/errors-handler.ts @@ -0,0 +1,32 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { ErrorHandler, Injectable } from '@angular/core'; +import { HttpErrorResponse } from '@angular/common/http'; + +@Injectable() +export class ErrorsHandler implements ErrorHandler { + // Do whatever you like with the error (send it to the server?) + // And log it to the console + handleError(error: Error | HttpErrorResponse) { + if (error instanceof HttpErrorResponse) { + // Server or connection error happened + if (navigator.onLine) { + if (error.url && error.url.endsWith('login')) { + window.location.reload(); + } else { + console.log(error); + } + } + } else { + console.log(error); + } + } +} diff --git a/web/client/src/app/find-iris/find-iris.service.spec.ts b/web/client/src/app/find-iris/find-iris.service.spec.ts new file mode 100644 index 0000000..f4f6d4f --- /dev/null +++ b/web/client/src/app/find-iris/find-iris.service.spec.ts @@ -0,0 +1,24 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { TestBed, inject } from '@angular/core/testing'; +import { FindIrisService } from './find-iris.service'; + +describe('XIII. The FindIrisService Class: ', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [FindIrisService] + }); + }); + + it('a. The class should be created', inject([FindIrisService], (service: FindIrisService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/web/client/src/app/find-iris/find-iris.service.ts b/web/client/src/app/find-iris/find-iris.service.ts new file mode 100644 index 0000000..f375109 --- /dev/null +++ b/web/client/src/app/find-iris/find-iris.service.ts @@ -0,0 +1,1105 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; +import { ToolManagerService } from '../iris-display/tool-manager.service'; +import { Rectangle, Point } from 'paper'; + +class ImageArray { + public data: Uint8ClampedArray; + public width = 0; + public height = 0; + + public getPixel(x, y) { + const tempx = Math.floor(x); + const tempy = Math.floor(y); + return this.data[tempx + (tempy * this.width)]; + } +} + +class ImageNumberArray { + public data: Array; + public width = 0; + public height = 0; + + public getPixel(x, y) { + const tempx = Math.floor(x); + const tempy = Math.floor(y); + return this.data[tempx + (tempy * this.width)]; + } + +} + +@Injectable() +export class FindIrisService { + + maxsegs = 256; + MaxR = 250; + trigLength = this.maxsegs * this.MaxR * 2; + trigVals = new Array(this.trigLength); + + trigPos = (i, j, h) => (i * this.MaxR * 2) + (j * 2) + h; + + edgeArrayLength = 0; + edgeValBin: ImageNumberArray = new ImageNumberArray(); + pupilEdgeBin: ImageNumberArray = new ImageNumberArray(); + + vertEdge: ImageArray; + + IrisCenterX = 0; + IrisCenterY = 0; + IrisRadius = 0; + + pupilCenterX = 0; + pupilCenterY = 0; + pupilRadius = 0; + pupilDiameter = 0; + + protected toolManager: ToolManagerService; + + constructor() { + } + + initialize(toolManager: ToolManagerService) { + this.toolManager = toolManager; + this.initMathTables(); + const raster = this.toolManager._iris; + const imageData: ImageData = this.toolManager._iris.getImageData(new Rectangle(0, 0, raster.width, raster.height)); + + const ret = this.findIris(imageData); + } + + findIris(imageData: ImageData): ImageData { + const grayData = this.grayScaleImage(imageData); + + const dsfactor = 4; + const dsImage = this.downScale(grayData, imageData.width, imageData.height, dsfactor); + const edgeRawBin = this.edgeMap(dsImage.data, dsImage.width, dsImage.height, dsfactor); + this.vertEdge = this.getVertEdges(dsImage.data, dsImage.width, dsImage.height); + this.findIrisCenter(dsImage.width, dsImage.height, imageData, dsImage, this.vertEdge, this.edgeValBin); + + const tempDsImage = new ImageArray(); + tempDsImage.data = dsImage.data; + tempDsImage.width = dsImage.width; + tempDsImage.height = dsImage.height; + this.findPupilCenter(this.edgeValBin, this.IrisCenterX, this.IrisCenterY, this.IrisRadius * 2, tempDsImage); + + const rgbData = this.grayToRgb(this.vertEdge.data); + const data = new ImageData(rgbData, this.vertEdge.width, this.vertEdge.height); + + return data; + } + + grayToRgb(imageData: Uint8ClampedArray): Uint8ClampedArray { + const rgbaLength = 4; + const ret = new Uint8ClampedArray(imageData.length * rgbaLength); + + for (let x = 0; x < imageData.length; x++) { + const xret = x * rgbaLength; + ret[xret] = imageData[x]; + ret[xret + 1] = imageData[x]; + ret[xret + 2] = imageData[x]; + ret[xret + 3] = 255; + } + + return ret; + } + + grayScaleImage(imageData: ImageData): Uint8ClampedArray { + //the data for the image is in rgba order + //and needs to be brought down to 8 bit grayscale + const rgbaLength = 4; + const ret = new Uint8ClampedArray(imageData.data.length / rgbaLength); + + for (let i = 0; i < imageData.data.length; i += 4) { + const r = i / 4; + ret[r] = (imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2]) / 3; + } + + return ret; + } + + //goes through original image and downscales the image by taking a box of + //pixels and scaling it down to one point + downScale(imageData: Uint8ClampedArray, imageWidth: number, imageHeight: number, downsizescale: number) { + let origx = 0; + let origy = 0; + + const averageboxsize = downsizescale * downsizescale; + + const dswidth = Math.floor(imageWidth / downsizescale); + const dsheight = Math.floor(imageHeight / downsizescale); + const ret = new Uint8ClampedArray(dswidth * dsheight); + + for (let indx = 0; indx < dswidth; indx++) { + origx = indx * downsizescale; + for (let indy = 0; indy < dsheight; indy++) { + origy = indy * downsizescale; + // for each pixel in the downsized image... + // get the average intensity over the downsizescale*downsizescale + // group of original pixels + let dspoint = 0; + for (let indxx = 0; indxx < downsizescale; indxx++) { + for (let indyy = 0; indyy < downsizescale; indyy++) { + let position = (indyy + origy) * imageWidth; + position += indxx + origx; + dspoint += imageData[position]; + } + } + + ret[indx + (indy * dswidth)] = dspoint / averageboxsize; + } + } + + return {data: ret, width: dswidth, height: dsheight}; + } + + // imageData - downscaled image + // imageWidth - downscaled image width + // imageHeight - downscaled image height + // downsizescale - scale used to downscale image + edgeMap(imageData: Uint8ClampedArray, imageWidth: number, imageHeight: number, downsizescale: number) { + + this.edgeArrayLength = Math.floor(imageHeight * imageWidth); + + this.edgeValBin.data = new Array(this.edgeArrayLength); + this.edgeValBin.width = imageWidth; + this.edgeValBin.height = imageHeight; + + this.pupilEdgeBin.data = new Array(this.edgeArrayLength); + this.pupilEdgeBin.width = imageWidth; + this.pupilEdgeBin.height = imageHeight; + + const edgeRawBin = new Uint8ClampedArray(this.edgeArrayLength); + + for (let i = 0; i < this.edgeArrayLength; i++) { + this.edgeValBin.data[i] = 0; + this.pupilEdgeBin.data[i] = 0; + } + + const imagePosition = (x, y) => x + (y * imageWidth); + + const isGreater = false; + for (let indy = downsizescale; indy < imageHeight - 1 - downsizescale; indy++) { + for (let kdx = 0; kdx < downsizescale; kdx++) { + // assign 0 edge intensity values to a 4 pixel wide boundary on the + // x axis note that this equates to a 16 pixel boundary on the + // original image since it has been downsized by factor of 4 + this.edgeValBin.data[imagePosition(kdx, indy)] = 0; + this.edgeValBin.data[imagePosition(imageWidth - downsizescale - 1 + kdx, indy)] = 0; + + this.pupilEdgeBin.data[imagePosition(kdx, indy)] = 0; + this.pupilEdgeBin.data[imagePosition(imageWidth - downsizescale - 1 + kdx, indy)] = 0; + + edgeRawBin[imagePosition(kdx, indy)] = 0; + edgeRawBin[imagePosition(imageWidth - downsizescale - 1 + kdx, indy)] = 0; + }//end of for + + for (let indx = downsizescale; indx < imageWidth - downsizescale; indx++) { + this.edgeValBin.data[imagePosition(indx, indy)] = 0; + this.pupilEdgeBin.data[imagePosition(indx, indx)] = 0; + + for (let jdx = 0; jdx < downsizescale; jdx++) { + // Takes 4 pixels to either side of the trial pixel + // Note that when this is done on a quarter downsized image - it is + // a 16 pixel wide average difference + + let imgpt1 = imageData[imagePosition(indx - jdx - 1, indy)]; + let imgpt2 = imageData[imagePosition(indx + jdx, indy)]; + + imgpt1 = imgpt1 > 200 ? 200 : imgpt1; + imgpt2 = imgpt2 > 200 ? 200 : imgpt2; + + this.edgeValBin.data[imagePosition(indx, indy)] += (imgpt1 - imgpt2); + + }//end of for loop + + if (this.edgeValBin.data[imagePosition(indx, indy)] < 0) { + this.edgeValBin.data[imagePosition(indx, indy)] *= -1; + } + + if (this.edgeValBin.data[imagePosition(indx, indy)] > 140) { + this.edgeValBin.data[imagePosition(indx, indy)] = 140; + } + + // Edge differences greater than 140 are likely due to + // specularities, not iris edges + this.edgeValBin.data[imagePosition(indx, indy)] /= 3; + edgeRawBin[imagePosition(indx, indy)] = this.edgeValBin.data[imagePosition(indx, indy)]; + + }//end of for + + }//end of for + + return { + data: edgeRawBin, + width: imageWidth, + height: imageHeight + }; + + }//end of edgemap + + + getVertEdges(dsImage: Uint8ClampedArray, imageWidth: number, imageHeight: number) { + + // Go horizontally across the downsized image and get average intensity + // differences along an 8 pixel vertical edge + // centered on the current horizontal point on each line + + const boxSize = 4; + const vertEdge = new Uint8ClampedArray(dsImage.length); + + const imagePosition = (x, y) => x + (y * imageWidth); + for (let idx8 = 0; idx8 < imageWidth; idx8++) { + // Assign zero vertical edge values in the first 4 vertical lines + for (let kdy = 0; kdy < boxSize; kdy++) { + // assign 0 edge intensity values to a 4 pixel wide boundary on the + // x axis note that this equates to a 16 pixel boundary on the + // original image since it has been downsized by factor of 4 + + vertEdge[imagePosition(idx8, kdy)] = 0; + vertEdge[imagePosition(idx8, imageHeight - kdy - 1)] = 0; + }//end of for + + for (let jdy8 = boxSize; jdy8 < imageHeight - (2 * boxSize); jdy8++) { + let pointVal = dsImage[imagePosition(idx8, jdy8 - 2)] + dsImage[imagePosition(idx8, jdy8 - 1)]; + pointVal = pointVal - dsImage[imagePosition(idx8, jdy8 + 1)] - dsImage[imagePosition(idx8, jdy8)]; + + pointVal = pointVal < 0 ? pointVal * -1 : pointVal; + pointVal = pointVal > 255 ? 255 : pointVal; + + vertEdge[imagePosition(idx8, jdy8)] = pointVal; + pointVal = pointVal & 0x000000ff; + pointVal = pointVal > 20 ? 20 : pointVal; + vertEdge[imagePosition(idx8, jdy8)] = pointVal; + }//end of for + } + + const ret = new ImageArray(); + ret.data = vertEdge; + ret.width = imageWidth; + ret.height = imageHeight; + return ret; + }//end of getVertEdges + + initMathTables() { + let anglesegnum = 0.0; + let radiuslength = 0.0; + const halfmaxsegs = this.maxsegs / 2; + const seg90 = this.maxsegs / 4; + const seg180 = this.maxsegs / 2; + let dsin = 0; + let dcos = 0; + const singlesegment = 6.28318530718 / this.maxsegs; //magic number? + + + for (let i = 0; i < this.trigLength; i++) { + this.trigVals[i] = 0; + } + + + for (anglesegnum = 0; anglesegnum < seg90 + 1; anglesegnum++) { + // calculate sine and cosines + dsin = Math.sin(singlesegment * anglesegnum); + dcos = Math.cos(singlesegment * anglesegnum); + for (radiuslength = 0; radiuslength < this.MaxR + 1; radiuslength++) { + // get x axis orinals in image array for a given angle and distance + // from center + if (dcos >= 0) { + this.trigVals[this.trigPos(anglesegnum, radiuslength, 0)] = Math.floor(dcos * radiuslength + 0.5); + } else { + this.trigVals[this.trigPos(anglesegnum, radiuslength, 0)] = Math.ceil(dcos * radiuslength - 0.5); + } + this.trigVals[this.trigPos(halfmaxsegs - anglesegnum, radiuslength, 0)] = -1 * this.trigVals[this.trigPos(anglesegnum, radiuslength, 0)] - 1; + this.trigVals[this.trigPos(halfmaxsegs + anglesegnum, radiuslength, 0)] = -1 * this.trigVals[this.trigPos(anglesegnum, radiuslength, 0)] - 1; + this.trigVals[this.trigPos(this.maxsegs - anglesegnum, radiuslength, 0)] = this.trigVals[this.trigPos(anglesegnum, radiuslength, 0)]; + + // now get y axis ordinal in image array for a given angle and + // distance from center + if (dsin >= 0) { + this.trigVals[this.trigPos(anglesegnum, radiuslength, 1)] = Math.floor(dsin * radiuslength + 0.5); + } else { + this.trigVals[this.trigPos(anglesegnum, radiuslength, 1)] = Math.ceil(dsin * radiuslength - 0.5); + } + this.trigVals[this.trigPos(halfmaxsegs - anglesegnum, radiuslength, 1)] = this.trigVals[this.trigPos(anglesegnum, radiuslength, 1)]; + this.trigVals[this.trigPos(halfmaxsegs + anglesegnum, radiuslength, 1)] = -1 * this.trigVals[this.trigPos(anglesegnum, radiuslength, 1)] - 1; + this.trigVals[this.trigPos(this.maxsegs - anglesegnum, radiuslength, 1)] = -1 * this.trigVals[this.trigPos(anglesegnum, radiuslength, 1)]; + } // radius + } + // special cases just to keep from referencing out-of-bounds values at this.MaxR + // since defined origin at lower left corner of (0,0) + this.trigVals[this.trigPos(0, this.MaxR, 0)] = this.MaxR - 1; + this.trigVals[this.trigPos(seg90, this.MaxR, 1)] = this.MaxR - 1; + this.trigVals[this.trigPos(halfmaxsegs, this.MaxR, 0)] = -this.MaxR; + this.trigVals[this.trigPos(this.maxsegs - seg90, this.MaxR, 1)] = -this.MaxR; + this.trigVals[this.trigPos(this.maxsegs, this.MaxR, 0)] = this.MaxR - 1; + } + + findIrisCenter(imageWidth: number, imageHeight: number, originalImage: ImageData, dsImage , vertEdge: any, edgeValBin: any) { + + // Version 2.2.1 change initialize darkiriscenter to zero + let darkiriscenter = 0; + let bestdarkiriscenter = 0; + + const iriscenterpt = new Array>(224); + for (let i = 0; i < 224; i++) { + iriscenterpt[i] = new Point(0, 0); + } + + // assume that Edgevalbin from above is used + // remember that image coordinants are from top left to bottom right + // MinRtrial = 35; + // Reminder that since this is on a half-sized image... + // correlates with 150 iris diameter min MaxRtrial = 100; // Max trial + // radius for 320x240 image... Max trial diameter is 400 on full-sized + // 640x480 image LowerSegTrial = 24; is the number of 360/256 degree + // segments below the horizontal axis of the iris UpperSegTrial = 12; is + // the number of segments above the horizontal axis of the iris + + // Version 2.2.1 Change ... LowerSegTrial and UpperSegTrial are globals... + // Reset these values due to conflict with Fine + // Iris finding routine. + const LowerSegTrial = 47; + const UpperSegTrial = 40; + + let MaxSegTrial = UpperSegTrial; + if (LowerSegTrial > MaxSegTrial) { + MaxSegTrial = LowerSegTrial; + } + + let bestresponse = 0; + let RoughIrisRadius = 0; + let RoughIriscx = 0; + let RoughIriscy = 0; + // set up the array of circular points close to the center to check for dark + // areas. set up the upper right quadrant and copy to the others + let ctr = 0; + let rdxx = 0; + let jdxx = 0; + for (rdxx = 2; rdxx < 17; + rdxx += + 2) { + // makes points up to 16 pixel (downsized) radius (8 samples) + // equivalent pupil diameter is 16*2 * 4 = 128 + for (jdxx = 0; jdxx < 61; + jdxx += + 10) { + // takes 10 angle segment increments up to 60 ( 7 samples ) + // creates 56 points in upper right trig quadrant (in relative + // coordinates, not image coords) + iriscenterpt[ctr].x = this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + iriscenterpt[ctr].y = this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + // creates additional 56 points in lower right, upper left and lower + // left quadrants + iriscenterpt[ctr + 56].x = this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + iriscenterpt[ctr + 56].y = -1 * this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + iriscenterpt[ctr + 112].x = -1 * this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + iriscenterpt[ctr + 112].y = this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + iriscenterpt[ctr + 168].x = -1 * this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + iriscenterpt[ctr + 168].y = -1 * this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + ctr++; + } + } + let maxIrisFindRightEdgeResponse = 0; + let maxIrisFindLeftEdgeResponse = 0; + let maxVertEdgeResponse = 0; + + let rdx = 0; + let idx = 0; + let idy = 0; + let jdx = 0; + let MaxRelX = 0; + let MinRelY = 0; + let MaxRelY = 0; + let rightedgepointsused = 0; + let leftedgepointsused = 0; + let trialresponse = 0; + let leftedgeresponse = 0; + let rightedgeresponse = 0; + let vertedgeresponse = 0; + let ptdarkval = 0; + let trialx = 0; + let trialy = 0; + let ictr5 = 0; + let srx = 0; + let suy = 0; + let slx = 0; + let sly = 0; + const MaxRtrial = 42; + const MinRtrial = 28; + + const getImagePosition = (imgData, x, y) => x + (y * imgData.width); + const counter = 0; + + for (rdx = MinRtrial; rdx < MaxRtrial; rdx++, rdx++) { + // calculate width and height of the test area for a given rdx,jdx + MaxRelX = rdx; // test cannot include X coordinants within the trial radius + MinRelY = this.trigVals[this.trigPos(LowerSegTrial, rdx, 1)]; + MaxRelY = this.trigVals[this.trigPos(UpperSegTrial, rdx, 1)]; + // remember that these are opposite image coords + for (idx = imageWidth * 3 / 10; idx < imageWidth * 7 / 10; idx++, idx++) { + for (idy = imageHeight * 3 / 10; idy < imageHeight * 7 / 10; idy++, idy++) { + rightedgepointsused = 0; + leftedgepointsused = 0; + trialresponse = 0; + leftedgeresponse = 0; + rightedgeresponse = 0; + vertedgeresponse = 0; + darkiriscenter = 0; + ptdarkval = 0; + trialx = 0; + trialy = 0; + + for (ictr5 = 0; ictr5 < 224; ictr5++) { + + // calculate "Dark Center response" for the current idx,idy + // note that the grid radius is always 5,10,15,20,25 + trialx = idx + iriscenterpt[ictr5].x; + trialy = idy - iriscenterpt[ictr5].y; + + if (trialx > 0 + && trialx < imageWidth + && trialy < imageHeight + && trialy > 0) { + + ptdarkval = dsImage.data[getImagePosition(dsImage, Math.floor(trialx), Math.floor(trialy))]; + ptdarkval = (55 - ptdarkval); + // in low contrast images to dark gray iris + if (ptdarkval < 0) { + ptdarkval = 0; + } + darkiriscenter += ptdarkval; + } + }//end of for + + for (jdx = 0; jdx < MaxSegTrial; jdx++) { + // right extent + srx = idx + this.trigVals[this.trigPos(jdx, rdx, 0)]; + // upper extent - remember lower y is upper + suy = idy - this.trigVals[this.trigPos(jdx, rdx, 1)]; + // left extent + slx = idx - this.trigVals[this.trigPos(jdx, rdx, 0)]; + // lower extent + sly = idy + this.trigVals[this.trigPos(jdx, rdx, 1)]; + + if ((srx + 4 < imageWidth - 1) && (srx - 2 >= 0)) { + + if ((suy >= 4) && (jdx < UpperSegTrial)) { + + rightedgeresponse += this.edgeValBin.getPixel(srx, suy); + vertedgeresponse += this.vertEdge.getPixel(srx, suy); + rightedgeresponse += this.edgeValBin.getPixel(srx - 1, suy); + vertedgeresponse += this.vertEdge.getPixel(srx - 1, suy); + rightedgeresponse += this.edgeValBin.getPixel(srx + 1, suy); + vertedgeresponse += this.vertEdge.getPixel(srx + 1, suy); + rightedgepointsused++; + } + + if ((sly < imageHeight - 3) && (jdx < LowerSegTrial)) { + + rightedgeresponse += this.edgeValBin.getPixel(srx, sly); + vertedgeresponse += this.vertEdge.getPixel(srx, sly); + rightedgeresponse += this.edgeValBin.getPixel(srx - 1, sly); + vertedgeresponse += this.vertEdge.getPixel(srx - 1, sly); + rightedgeresponse += this.edgeValBin.getPixel(srx + 1, sly); + vertedgeresponse += this.vertEdge.getPixel(srx + 1, sly); + rightedgepointsused++; + } + } + + // Left Edge response + if ((slx - 2 >= 0) && (srx + 4 < imageWidth - 1)) { + if ((suy >= 4) && (jdx < UpperSegTrial)) { + leftedgeresponse += this.edgeValBin.getPixel(slx, suy); + vertedgeresponse += this.vertEdge.getPixel(slx, suy); + leftedgeresponse += this.edgeValBin.getPixel(slx - 1, suy); + vertedgeresponse += this.vertEdge.getPixel(slx - 1, suy); + leftedgeresponse += this.edgeValBin.getPixel(slx + 1, suy); + vertedgeresponse += this.vertEdge.getPixel(slx + 1, suy); + leftedgepointsused++; + } + if ((sly < imageHeight - 1) && + (jdx < LowerSegTrial)) { + // lower left edge response + leftedgeresponse += this.edgeValBin.getPixel(slx, sly); + vertedgeresponse += this.vertEdge.getPixel(slx, sly); + leftedgeresponse += this.edgeValBin.getPixel(slx - 1, sly); + vertedgeresponse += this.vertEdge.getPixel(slx - 1, sly); + leftedgeresponse += this.edgeValBin.getPixel(slx + 1, sly); + vertedgeresponse += this.vertEdge.getPixel(slx + 1, sly); + leftedgepointsused++; + } + } + } // end of loop for angle segments to be added up + // weighting factor for darkiriscenter + darkiriscenter = darkiriscenter * 105 / 10; // 100 used in test - works with "dark threshold" of 55 + vertedgeresponse = vertedgeresponse * 3 / 10; + trialresponse = leftedgeresponse * 15 / 10 + + rightedgeresponse * 15 / 10 + + vertedgeresponse + + darkiriscenter; + if (trialresponse > bestresponse) { + bestresponse = trialresponse; + RoughIriscx = idx * 4; + RoughIriscy = idy * 4; + RoughIrisRadius = rdx * 4; + this.IrisCenterX = idx * 4; + this.IrisCenterY = idy * 4; + this.IrisRadius = rdx * 4; + maxIrisFindRightEdgeResponse = rightedgeresponse; + maxIrisFindLeftEdgeResponse = leftedgeresponse; + maxVertEdgeResponse = vertedgeresponse; + bestdarkiriscenter = darkiriscenter; + } + } // end of y loop + } // end of x loop + } // end of R loop + // check for strong vertical edges along the iris-sclera boundary as an + // indicator of occlusions + this.findFineIris(originalImage, imageWidth * 4, imageHeight * 4, this.IrisCenterX, this.IrisCenterY, this.IrisRadius * 2); + } + + findFineIris(originalImage: ImageData, imageWidth: number, imageHeight: number, roughIxc: number, roughIyc: number, roughIdiam) { + + // set the bounds of the search area based upon rough locations + const MinRfine = roughIdiam / 2 - 4; + let MaxRfine = roughIdiam / 2 + 4; + if (MaxRfine > this.MaxR) { + MaxRfine = this.MaxR; + } + const MinIxcfine = roughIxc - 4; + const MaxIxcfine = roughIxc + 4; + const MinIycfine = roughIyc - 4; + const MaxIycfine = roughIyc + 4; + const bestresponse = 0; + const leftresponse = 0; + const rightresponse = 0; + let wf = 1; + const TanPts = new Array>(33); + for (let i = 0; i < TanPts.length; i++) { + TanPts[i] = new Point(0, 0); + } + + let rightedgepointsused = 0; + let leftedgepointsused = 0; + let trialresponse = 0; + let leftedgeresponse = 0; + let rightedgeresponse = 0; + const maxpointresponse = 2000; + let srx = 0; + let suy = 0; + let slx = 0; + let sly = 0; + let idx = 0; + let jdx = 0; + let idy = 0; + let rctr = 0; + let jjdx = 0; + + // 47 works 256 / 4 = 64 + const LowerSegTrial = 40; + // 35 works higher fails on droopy eyelids + // cause failure lower fails on large pupils + const UpperSegTrial = 30; + + //magic number pulled from original that is supposed to be the original value of LowerSegTrial + const uppersegpointresponses = new Array(47); + const maxuppersegresponses = new Array(47); + for (let i = 0; i < 47; i++) { + uppersegpointresponses[i] = 0; + maxuppersegresponses[i] = 0; + } + + const getImagePosition = (imgData, x, y) => x + (y * imgData.width); + for (let rdx = MinRfine; rdx < MaxRfine; rdx++) { + for (idx = MinIxcfine; idx < MaxIxcfine; idx++) { + for (idy = MinIycfine; idy < MaxIycfine; idy++) { + rightedgepointsused = 0; + leftedgepointsused = 0; + trialresponse = 0; + leftedgeresponse = 0; + rightedgeresponse = 0; + + for (jdx = 0; jdx < LowerSegTrial; jdx += 2) { + + uppersegpointresponses[jdx] = 0; + // fix for the fact that the radial will extend an + // additional 12-16 pixels! Right extent + srx = idx + this.trigVals[this.trigPos(jdx, rdx + 16, 0)]; + // Upper extent - remember lower y is upper + suy = idy - this.trigVals[this.trigPos(jdx, rdx + 16, 1)]; + // Left extent + slx = idx - this.trigVals[this.trigPos(jdx, rdx + 16, 0)]; + // Lower extent + sly = idy + this.trigVals[this.trigPos(jdx, rdx + 16, 1)]; + // weighting factor to give more weight to pixels + // closest to the exact edge + wf = 1; + + // Check to see if + // this is correct on + // the full size image + if ((srx < imageWidth) && (slx >= 0)) { + if ((suy >= 0) && (jdx < UpperSegTrial)) { + let pointresponse = 0; + for (rctr = 0; rctr < 16; rctr++, rctr++) { + wf = 4 - (rctr / 4); + const tempx = idx + this.trigVals[this.trigPos(jdx, rdx + rctr, 0)]; + const tempy = idy - this.trigVals[this.trigPos(jdx, rdx + rctr, 1)]; + + const tempx2 = idx + this.trigVals[this.trigPos(jdx, rdx - rctr, 0)]; + const tempy2 = idy - this.trigVals[this.trigPos(jdx, rdx - rctr, 1)]; + + const tempMul = originalImage.data[getImagePosition(originalImage, tempx, tempy)]; + const tempMul2 = originalImage.data[getImagePosition(originalImage, tempx2, tempx2)]; + pointresponse += wf * (tempMul - tempMul2); + } + + if (pointresponse < 0) { + pointresponse = -pointresponse; + } + + if (pointresponse > maxpointresponse) { + pointresponse = maxpointresponse; + } + + uppersegpointresponses[jdx] = pointresponse; + rightedgeresponse += pointresponse; + rightedgepointsused++; + } + + if ((sly < imageWidth) && (jdx < LowerSegTrial)) { + let pointresponse = 0; + for (rctr = 0; rctr < 16; rctr++) { + wf = 4 - (rctr / 4); + const tempx1 = idx + this.trigVals[this.trigPos(jdx, rdx + rctr, 0)]; + const tempy1 = idy + this.trigVals[this.trigPos(jdx, rdx + rctr, 1)]; + + const tempx2 = idx + this.trigVals[this.trigPos(jdx, rdx - rctr, 0)]; + const tempy2 = idy + this.trigVals[this.trigPos(jdx, rdx - rctr, 1)]; + + const tempMul = originalImage.data[getImagePosition(originalImage, tempx1, tempy1)]; + const tempMul2 = originalImage.data[getImagePosition(originalImage, tempx2, tempx2)]; + + pointresponse += wf * (tempMul - tempMul2); + // watch out for X values that go beyond the + // image + } + + if (pointresponse < 0) { + pointresponse = -pointresponse; + } + + if (pointresponse > maxpointresponse) { + pointresponse = maxpointresponse; + } + + rightedgeresponse += pointresponse; + rightedgepointsused++; + } + } + + if ((slx >= 0) && (srx <= imageWidth)) { + if ((suy >= 0) && (jdx < UpperSegTrial)) { + let pointresponse = 0; + for (rctr = 0; rctr < 16; rctr++) { + wf = 4 - (rctr / 4); + const tempx = idx - this.trigVals[this.trigPos(jdx, rdx + rctr, 0)]; + const tempy = idy - this.trigVals[this.trigPos(jdx, rdx + rctr, 1)]; + + const tempx2 = idx - this.trigVals[this.trigPos(jdx, rdx - rctr, 0)]; + const tempy2 = idy - this.trigVals[this.trigPos(jdx, rdx - rctr, 1)]; + + const tempMul = originalImage.data[getImagePosition(originalImage, tempx, tempy)]; + const tempMul2 = originalImage.data[getImagePosition(originalImage, tempx2, tempx2)]; + + pointresponse += wf * (tempMul - tempMul2); + } + + if (pointresponse < 0) { + pointresponse = -pointresponse; + } + + if (pointresponse > maxpointresponse) { + pointresponse = maxpointresponse; + } + + uppersegpointresponses[jdx] += pointresponse; + leftedgeresponse += pointresponse; + leftedgepointsused++; + } + + if ((sly < imageHeight - 1) && (jdx < LowerSegTrial)) { + // fill in logic to get the lower left response + let pointresponse = 0; + for (rctr = 0; rctr < 16; rctr++) { + wf = 4 - (rctr / 4); + const tempx1 = idx - this.trigVals[this.trigPos(jdx, rdx + rctr, 0)]; + const tempy1 = idy + this.trigVals[this.trigPos(jdx, rdx + rctr, 1)]; + + const tempx2 = idx - this.trigVals[this.trigPos(jdx, rdx - rctr, 0)]; + const tempy2 = idy + this.trigVals[this.trigPos(jdx, rdx - rctr, 1)]; + + const tempMul = originalImage.data[getImagePosition(originalImage, tempx1, tempy1)]; + const tempMul2 = originalImage.data[getImagePosition(originalImage, tempx2, tempx2)]; + pointresponse += wf * (tempMul - tempMul2); + + } + + if (pointresponse < 0) { + pointresponse = -pointresponse; + } + + if (pointresponse > maxpointresponse) { + pointresponse = maxpointresponse; + } + + leftedgeresponse += pointresponse; + leftedgepointsused++; + }//end of if + } + } // end of loop for angle segments to be added up + trialresponse = leftedgeresponse + rightedgeresponse; + + if (trialresponse > bestresponse) { + + this.IrisCenterX = idx; + this.IrisCenterY = idy; + this.IrisRadius = rdx; + + // now save the upperedgepointresponses for later use + for (jjdx = 0; jjdx < UpperSegTrial; jjdx++) { + maxuppersegresponses[jjdx] = uppersegpointresponses[jjdx]; + } + } + } // end of y loop + } // end of x loop + } // end of R loop + // Use the best diameter to calculate diagnostic metrics: + // Get Iris-Sclera GrayScale Difference values over a -60 to 45 deg arc + // along the diameter Use response value trends along the arc on left and + // right sides to determine likely eyelid locations Fill in ISGSVals[256]; + // // 0 is 0 degrees // 64 is 90 deg. //128 is 180 deg //192 is 270 deg. + const IXC = this.IrisCenterX; + const IYC = this.IrisCenterY; + const IRAD = this.IrisRadius; + + const ISGSVals = new Array(256); + for (let i = 0; i < ISGSVals.length; i++) { + ISGSVals[i] = 0; + } + + if (this.IrisRadius > this.MaxR - 16) { + console.error('Iris Radius exceeds max allowable'); + return; + } + + if (this.IrisRadius < 17) { + console.error('Invalid Iris Diameter value', this.IrisRadius); + return; + } + + let rctr2 = 0; + let outerpointval = 0; + let innerpointval = 0; + for (let jdxx = 0; jdxx < this.maxsegs; jdxx++) { + // in this array TanPts[33] is the centered on the iris edge arc + for (rctr = 0; rctr < 33; rctr++) { + TanPts[rctr].x = IXC + this.trigVals[this.trigPos(jdxx, IRAD + rctr - 16, 0)]; + TanPts[rctr].y = IYC - this.trigVals[this.trigPos(jdxx, IRAD + rctr - 16, 1)]; + } + let pointresponse = 0; + let numradpts = 0; + for (rctr2 = 1; rctr2 < 16; rctr2++) { + if (TanPts[16 + rctr2].x > 0 + && TanPts[16 + rctr2].x < imageWidth + && TanPts[16 + rctr2].y > 0 + && TanPts[16 + rctr2].y < imageHeight) { + wf = 1; + const tempx = TanPts[16 + rctr2].x; + const tempy = TanPts[16 + rctr2].y; + const tempx2 = TanPts[16 - rctr2].x; + const tempy2 = TanPts[16 - rctr2].y; + + // weighting factor for distance from iris edge point going 16 + // pixels each direction wf = 4 - (rctr2 / 4); + outerpointval = originalImage.data[getImagePosition(originalImage, tempx, tempy)]; + innerpointval = originalImage.data[getImagePosition(originalImage, tempx2, tempy2)]; + // Version 2.2.1 Change to limit effects of single differences) + let responseint = outerpointval - innerpointval; + if (responseint > 75) { + responseint = 75; + } + pointresponse = pointresponse + responseint; + numradpts++; + } else { + pointresponse = 0; + } + } + ISGSVals[jdxx] = pointresponse; + } // end of jdxx - intensity difference for each radial segment from 0 to + // maxsegs at IXC, IYC, IRAD + // Calculate adjusted Iris-Sclera GrayScale Difference using averages of + // ISGSVals[128:138] and ISGSVals[246:0]; + let isgstotal = 0; + + // ISGSDiffMeanAvg = isgstotal / 300; // remember that each point in + // ISGSVals is 15 pixels on each side + let ptsused = 0; + for (let jctr2 = 0; jctr2 < 40; jctr2++) { + // Each point in ISGSVals is 15 pixels on each side + isgstotal = isgstotal + ISGSVals[255 - jctr2]; + ptsused++; + isgstotal = isgstotal + ISGSVals[128 + jctr2]; + ptsused++; + } + + for (let jctr3 = 0; jctr3 < 20; jctr3++) { + // Each point in ISGSVals is 15 pixels on each side + // Add response on 20 upper segments + isgstotal = isgstotal + ISGSVals[jctr3]; + ptsused++; + isgstotal = isgstotal + ISGSVals[127 - jctr3]; + ptsused++; + } + + const ISGSDiffMeanAvg = isgstotal / (ptsused * 15); // Remember that each point in ISGSVals is 15 pixels on each side + } + + + findPupilCenter(edgeData: ImageNumberArray, irisCenterX: number, irisCenterY: number, irisDiameter: number, dsImage: ImageArray) { + // Use same routine as Find Iris but restrict search to +/-0.1Idiam + // inside iris region and limit radius range to 0.8 irisd. This means + // that pupil center is restricted to within 40% of Iris diameter from + // Iris center. Also restrict trial iris radius so that it does not get + // within 8 pixels from iris edge + // the division by 4 is because the edgeData and the dsImage have been + // downscaled by 4 + const irisreduced = (irisDiameter / 4) / 10; + let roitop = (irisCenterY - irisreduced) / 4; + let roileft = (irisCenterX - irisreduced) / 4; + let roibottom = (irisCenterY + irisreduced) / 4; + let roiright = (irisCenterX + irisreduced) / 4; + let PupIcdist = 0; + let ipgsresponse = 0; + let MaxPupilEdgeResponse = 0; + let edgeresponse = 0; + const pwf = 0; + + const counter = 0; + + const pupilcenterpt = new Array>(224); + for (let i = 0; i < pupilcenterpt.length; i++) { + pupilcenterpt[i] = new Point(0, 0); + } + + let pctr = 0; + let rdxx = 0; + let jdxx = 0; + + // makes points up to 45 radius = 100 pixel diameter (8 samples) + for (rdxx = 3; rdxx < 25; rdxx += 3) { + // this is in downsized (by factor of 4) dimensions + // takes 10 angle segment increments up to 60 ( 7 samples ) + for (jdxx = 0; jdxx < 61; jdxx += 10) { + // creates 56 points in upper right trig quadrant (in relative + // coordinates, not image coords) + pupilcenterpt[pctr].x = this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + pupilcenterpt[pctr].y = this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + // creates additional 56 points in lower right, upper left and + // lower left quadrants + pupilcenterpt[pctr + 56].x = this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + pupilcenterpt[pctr + 56].y = -1.0 * this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + pupilcenterpt[pctr + 112].x = -1.0 * this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + pupilcenterpt[pctr + 112].y = this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + pupilcenterpt[pctr + 168].x = -1.0 * this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + pupilcenterpt[pctr + 168].y = -1.0 * this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + pctr++; + } + } + let ipgspointsused = 0; + + const PUpperSegTrial = 60; + const PLowerSegTrial = 63; + // make sure ROI is inside the image + if (roitop < 0) { + roitop = 0; + } + + if (roileft < 0) { + roileft = 0; + } + + if (roiright > edgeData.width - 1) { + roiright = edgeData.width - 1; + } + + if (roibottom > edgeData.height - 1) { + roibottom = edgeData.height - 1; + } + + let PMaxSegTrial = PUpperSegTrial; + if (PLowerSegTrial > PMaxSegTrial) { + PMaxSegTrial = PLowerSegTrial; + } + + let bestdarkpupilresponse = 0; + let darkpupilval = 0; + let bestresponse = 0; + + MaxPupilEdgeResponse = 0; + edgeresponse = 0; + let pupilCx = 0; + let pupilCy = 0; + let pupilD = 0; + let pupilRad = 0; + // for each trial radius rdx and for angle range jdx... find the best + // total response for the image + + const MaxPrtrial = (this.IrisRadius / 4) * 0.8; // Use 80% of IrisRadius + let rightedgepointsused = 0; + let leftedgepointsused = 0; + let rdx = 0; + let idx = 0; + let idy = 0; + let jdx = 0; + let ptdarkval = 0; + let trialx = 0; + let trialy = 0; + let pctr5 = 0; + let trialresponse = 0; + let leftedgeresponse = 0; + let rightedgeresponse = 0; + let srx = 0; + let suy = 0; + let slx = 0; + let sly = 0; + const MinPrtrial = 8; + for (rdx = MinPrtrial; rdx < MaxPrtrial; rdx++) { + // calculate width and height of the test area for a given rdx,jdx + for (idx = roileft; idx < roiright; idx++) { + for (idy = roitop; idy < roibottom; idy++) { + darkpupilval = 0; + ptdarkval = 0; + trialx = 0; + trialy = 0; + for (pctr5 = 0; pctr5 < 224; pctr5++) { + // calculate "Dark Center response" for the current idx, + // idy note that the grid radius is always 5,10,15,20,25 + trialx = idx + pupilcenterpt[pctr5].x; + trialy = idy - pupilcenterpt[pctr5].y; + if (trialx > 0 && trialx < edgeData.width && + trialy < edgeData.height && trialy > 0) { + ptdarkval = dsImage.getPixel(trialx, trialy); + ptdarkval = 130 - ptdarkval; + if (ptdarkval < 0) { + ptdarkval = 0; + } + ptdarkval /= 5; + darkpupilval = darkpupilval + ptdarkval; + } + } + + ipgsresponse = 0; + ipgspointsused = 0; + rightedgepointsused = 0; + leftedgepointsused = 0; + trialresponse = 0; + leftedgeresponse = 0; + rightedgeresponse = 0; + + for (jdx = 0; jdx < PMaxSegTrial; jdx++, jdx++) { + srx = idx + this.trigVals[this.trigPos(jdx, rdx, 0)]; // Right extent + suy = idy - this.trigVals[this.trigPos(jdx, rdx, 1)]; // Upper extent - + // remember lower y is upper + slx = idx - this.trigVals[this.trigPos(jdx, rdx, 0)]; // Left extent + sly = idy + this.trigVals[this.trigPos(jdx, rdx, 1)]; // Lower extent + + if ((srx + 4 < edgeData.width - 1) && (srx - 4 >= 0)) { + if ((suy >= 0) && (jdx < PUpperSegTrial)) { + rightedgeresponse += 2 * edgeData.getPixel(srx, suy); + rightedgeresponse += edgeData.getPixel(srx - 1, suy); + rightedgeresponse += edgeData.getPixel(srx + 1, suy); + rightedgepointsused++; + } + if ((sly < edgeData.height - 1) && + (jdx < PLowerSegTrial)) { + rightedgeresponse += 2 * edgeData.getPixel(srx, sly); + rightedgeresponse += edgeData.getPixel(srx - 1, sly); + rightedgeresponse += edgeData.getPixel(srx + 1, sly); + rightedgepointsused++; + } + } + if ((slx - 4 >= 0) && (srx + 4 <= edgeData.width - 1)) { + if ((suy >= 0) && (jdx < PUpperSegTrial)) { + leftedgeresponse += 2 * edgeData.getPixel(slx, suy); + leftedgeresponse += edgeData.getPixel(slx - 1, suy); + leftedgeresponse += edgeData.getPixel(slx + 1, suy); + leftedgepointsused++; + } + if ((sly < edgeData.height - 1) && + (jdx < PLowerSegTrial)) { + leftedgeresponse += 2 * edgeData.getPixel(slx, sly); + leftedgeresponse += edgeData.getPixel(slx - 1, sly); + leftedgeresponse += edgeData.getPixel(slx + 1, sly); + leftedgepointsused++; + } + } + } // end of loop for angle segments to be added up + if (leftedgepointsused + rightedgepointsused) { + edgeresponse = (leftedgeresponse + rightedgeresponse) * + 100 / + (leftedgepointsused + rightedgepointsused); + } else { + edgeresponse = 0; + } + darkpupilval *= 2; // 4 + trialresponse = edgeresponse + darkpupilval; + + + if (trialresponse > bestresponse) { + bestresponse = trialresponse; + MaxPupilEdgeResponse = edgeresponse; + bestdarkpupilresponse = darkpupilval; + pupilCx = (idx - 1) * 4; + pupilCy = (idy) * 4; + pupilRad = (rdx) * 4; + pupilD = rdx * 8; + } + + { + PupIcdist = Math.sqrt(((irisCenterY - idy) * (irisCenterY - idy) + (irisCenterX - idx) * (irisCenterX - idx))); + if ((PupIcdist + rdx) > (irisDiameter / 2) * 65 / 100 || + (idy + rdx) > (irisCenterY + (irisDiameter / 2) * 65 / 100) || + (idy - rdx) < (idy - (irisCenterY / 2) * 65 / 100)) { + // combination of trial radius rdx and trial pupilCx + // (idx), pupilCy (idy) is within 85% of the edge of + // the Iris so stop the loop + idy = roibottom; + } + }//end of block + } // end of y loop + + if ((idx + rdx) > (irisCenterX + (irisDiameter / 2) * 65 / 100) || + (irisCenterX - rdx) < (idx - (irisDiameter / 2) * 65 / 100)) { + // combination of trial radius rdx and trial pupilCx (idx), + // pupilCy (idy) is within 85% of the edge of the Iris so + // stop the loop + idx = roiright; + } + } // end of x loop + } // end of R loop ROUGH PUPIL RADIUS and Center point Done + + this.pupilCenterX = pupilCx; + this.pupilCenterY = pupilCy; + this.pupilRadius = pupilRad; + this.pupilDiameter = pupilRad * 2; + } +} \ No newline at end of file diff --git a/web/client/src/app/history/history.component.css b/web/client/src/app/history/history.component.css new file mode 100644 index 0000000..60bff2e --- /dev/null +++ b/web/client/src/app/history/history.component.css @@ -0,0 +1,28 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +.isFinished { + text-align: center; + height: 40px; +} + +.caret-ascend { + transform: scaleY(-1); +} + +.caret-descend { + transform: scaleY(1); +} + +.divider { + width: 5px; + height: auto; + display: inline-block; +} diff --git a/web/client/src/app/history/history.component.html b/web/client/src/app/history/history.component.html new file mode 100644 index 0000000..e66690a --- /dev/null +++ b/web/client/src/app/history/history.component.html @@ -0,0 +1,140 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Creation Date{{ getDateString(request.creationDateEpoch / 1000) }}Case Prefix{{ request.message.cinPrefix }}Case Identifier{{ request.message.cinIdentifier }}Case Extension{{ request.message.caseExtension }}Destination Agency{{ request.message.destinationAgencyIdentifier }}Originating Agency{{ request.message.originatingAgencyIdentifier }}Completedcheck + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Comparison Date{{ getDateString(request.caseInformation.comparisonDate / 1000) }}Transaction Control Number{{ request.probe.transactionControlNumber }}Response Type{{ request.responseType }}# of Candidates{{ request.candidates.length }}Requested By{{ request.caseInformation.requestedBy.name }}Completed{{ request.isFinished }} + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Received Date{{ getDateFromString(notification.dateCreated) }}Message{{notification.message}}Status{{notification.isNew === true ? 'New' : 'Read'}}Action + {{notification.action.includes('download') === true ? 'Download' : 'Navigate' }} + + Download + + + +
+ +
+
+
+
+
diff --git a/web/client/src/app/history/history.component.spec.ts b/web/client/src/app/history/history.component.spec.ts new file mode 100644 index 0000000..8ce99f5 --- /dev/null +++ b/web/client/src/app/history/history.component.spec.ts @@ -0,0 +1,63 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { HistoryComponent } from './history.component'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { DatePipe } from '@angular/common'; +import { MatSortModule } from '@angular/material/sort'; +import { MatTableModule } from '@angular/material/table'; +import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations'; + +describe('XIV. The HistoryComponent class:', () => { + let component: HistoryComponent; + let fixture: ComponentFixture; + const epoch = Math.floor(new Date().getTime() / 1000); + const DEBUG = false; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ MatSnackBarModule, MatSortModule, MatTableModule, RouterTestingModule, HttpClientTestingModule, BrowserAnimationsModule, NoopAnimationsModule ], + declarations: [ HistoryComponent ], + providers: [DatePipe], + schemas: [ CUSTOM_ELEMENTS_SCHEMA ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HistoryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('a. getDateString takes an epoch date as a paremeter and returns that date as a string (local time).', () => { + const dtString = component.getDateString(epoch); + expect(dtString).toBeInstanceOf(String); + + if (DEBUG) { + console.log(`UNIT TEST XIV(a): getDateString converted the epoch ${epoch} to local datetime string ${dtString}.`); + } + + }); + + it('b. getDateFromString takes a date as a string (local time) as a paremeter and returns a shortened version of the date.', () => { + const dtString = component.getDateString(epoch); + const stringToEpoch = component.getDateFromString(dtString); + expect(stringToEpoch).toBeInstanceOf(String); + + if (DEBUG) { + console.log(`UNIT TEST XIV(b): getDateFromString converted the long-form datetime string ${dtString} to this: ${stringToEpoch}.`); + } + }); +}); diff --git a/web/client/src/app/history/history.component.ts b/web/client/src/app/history/history.component.ts new file mode 100644 index 0000000..4bfdb18 --- /dev/null +++ b/web/client/src/app/history/history.component.ts @@ -0,0 +1,232 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component, OnInit, ViewChild } from '@angular/core'; +import { HistoryService } from './history.service'; +import { ProfileService } from '../profile/profile.service'; +import { CreateIrisSearchRequest } from '../types/create-iris-search-request'; +import { Router } from '@angular/router'; +import { ReportData } from '../types/report-data'; +import { MatTableDataSource } from '@angular/material/table'; +import { MatSort } from '@angular/material/sort'; +import { MatPaginator } from '@angular/material/paginator'; +import { NotificationsService } from '../notifications/notifications.service'; +import { AppNotification } from '../types/notification'; +import { DatePipe } from '@angular/common'; + +@Component({ + selector: 'app-history', + templateUrl: './history.component.html', + styleUrls: ['./history.component.css'] +}) + +export class HistoryComponent implements OnInit { + + searchRequests: Array = []; + searchRequestData: MatTableDataSource; + searchRequestColumns = ['creationDateEpoch', 'cinPrefix', 'cinIdentifier', 'caseExtension', + 'destinationAgencyIdentifier', 'originatingAgencyIdentifier', 'isFinished', 'actions']; + @ViewChild('searchSort', {static: true}) searchRequestSort: MatSort; + @ViewChild('searchPaginator', {static: true}) searchPaginator: MatPaginator; + + reviewRequests: Array = []; + reviewRequestData: MatTableDataSource; + reviewRequestColumns = ['comparisonDate', 'transactionControlNumber', 'responseType', + 'candidateCount', 'requestedBy', 'isFinished', 'actions']; + @ViewChild('reviewSort') reviewRequestSort: MatSort; + @ViewChild('reviewPaginator', {static: true}) reviewPaginator: MatPaginator; + + + userNotifications: Array = []; + userNotificationData: MatTableDataSource; + userNotificationColumns = ['receivedDate', 'message', 'status', 'action']; + @ViewChild('notificationPaginator', {static: true}) notificationPaginator: MatPaginator; + + pageSizeOptions = [10, 25, 50]; + + stepSize = 20; + searchStart = 0; + searchLength = 0; + reviewStart = 0; + reviewLength = 0; + + searchSortColumn = 'creationDateEpoch'; + reviewSortColumn = 'caseInformation.comparisonDate'; + caretScaling = 'caret-descend'; + + constructor( + private notificationService: NotificationsService, + private historyService: HistoryService, + public profileService: ProfileService, + private router: Router, + private datePipe: DatePipe + ) { + const settings = this.profileService.getCurrentProfile().userSetting; + this.stepSize = settings.numHistory; + } + + ngOnInit() { + this.updateSearches(); + + this.updateReviews(); + + this.getNotificationProfile(); + + this.historyService.getSearchCount().subscribe((len) => { + this.searchLength = len; + }); + + this.historyService.getReviewCount().subscribe((len) => { + this.reviewLength = len; + }); + + + } + + continueIrisSearch(searchRequestIndex, request) { + this.historyService.savedIrisSearchRequest = this.searchRequests[searchRequestIndex]; + this.router.navigate(['/create']); + } + + continueReview(reviewIndex, request) { + this.historyService.savedReview = this.reviewRequests[reviewIndex]; + this.router.navigate(['/review', request.id]); + } + + getDateString(epochDate: number) { + const d = new Date(0); + d.setUTCSeconds(epochDate); + return d.toLocaleString(); + } + + getDateFromString(dateString: string) { + const date = new Date(dateString); + + return this.datePipe.transform(date, 'short'); + } + + private updateSearches() { + const dir = this.caretScaling == 'caret-descend' ? 'desc' : 'asc'; + this.historyService.getIrisSearchCreateList(this.searchStart, this.stepSize, this.searchSortColumn, dir) + .subscribe((requests) => { + this.searchRequests = requests; + this.searchRequestData = new MatTableDataSource(requests); + this.searchRequestData.sortingDataAccessor = (item, property) => { + let value; + switch (property) { + case 'isFinished': + value = item.isFinished; + break; + case 'creationDateEpoch': + value = item.creationDateEpoch; + break; + default: + // Every other property is a actually a sub-property of the 'message' object + value = item.message[property]; + } + + return value; + }; + this.searchRequestData.paginator = this.searchPaginator; + this.searchRequestData.sort = this.searchRequestSort; + }); + } + + private updateReviews() { + const dir = this.caretScaling == 'caret-descend' ? 'desc' : 'asc'; + + this.historyService.getAllReviewList(this.reviewStart, this.stepSize, this.reviewSortColumn, dir) + .subscribe((requests) => { + this.reviewRequests = requests; + this.reviewRequestData = new MatTableDataSource(requests); + this.reviewRequestData.sortingDataAccessor = (item, property) => { + let value; + switch (property) { + case 'comparisonDate': + value = item.caseInformation.comparisonDate; + break; + case 'transactionControlNumber': + value = item.probe.transactionControlNumber; + break; + case 'candidateCount': + value = item.candidates.length; + break; + case 'requestedBy': + value = item.caseInformation.requestedBy.name; + break; + default: + value = item[property]; + } + + return value; + }; + this.reviewRequestData.paginator = this.reviewPaginator; + this.reviewRequestData.sort = this.reviewRequestSort; + }); + } + + //UserNotifications + private getNotificationProfile() { + let currentProfileId = this.profileService.getCurrentProfile().id; + if (currentProfileId == null || currentProfileId == '') { + this.profileService.currentProfile$.subscribe((profile) => { + this.getNotifications(profile.id); + }); + } else { + this.getNotifications(this.profileService.getCurrentProfile().id); + } + } + + private getNotifications(userId: string) { + this.notificationService.getAllNotificationsForUser(userId) + .subscribe((requests) => { + + this.userNotifications = requests; + + this.userNotifications = this.userNotifications.sort(this.sortNotifications); + this.userNotificationData = new MatTableDataSource(this.userNotifications); + this.userNotificationData.paginator = this.notificationPaginator; + }); + } + + private sortNotifications(a: AppNotification, b: AppNotification) { + return a.dateCreated > b.dateCreated ? -1 : 1; + } + + // this is the parent behavior. + markNotificationRead(not) { + this.notificationService.markSeen(not) + .subscribe((requests) => { + not.status = status; + this.historyService.notificationsModified('status'); + }); + } + + deleteNotification(not) { + this.notificationService.deleteNote(not) + .subscribe((not2) => { + const index: number = this.userNotifications.indexOf(not); + if (index !== -1) { + this.userNotifications.splice(index, 1); + this.historyService.notificationsModified('delete'); + } + }); + } + + checkNotificationAction(note) { + + if (note.action.includes('download')) { + window.open(note.action); + } else { + this.router.navigateByUrl(note.action).then(res => { + }); + } + } +} diff --git a/web/client/src/app/history/history.service.spec.ts b/web/client/src/app/history/history.service.spec.ts new file mode 100644 index 0000000..1da190f --- /dev/null +++ b/web/client/src/app/history/history.service.spec.ts @@ -0,0 +1,78 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { TestBed, inject } from '@angular/core/testing'; +import { HistoryService } from './history.service'; +import { Observable } from 'rxjs'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { CreateIrisSearchRequest } from '../types/create-iris-search-request'; + +describe(`XV. The HistoryService component class:`, () => { + const start = 10; + const limit = 17; + const column = 'foo'; + const dir = 'bar'; + const id = 'spam'; + const iris = new CreateIrisSearchRequest(); + const reviewId = 'John Doe'; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [HistoryService] + }); + }); + + it(`a. getIrisSearchCreateList takes two numbers like ${start} and ${limit} and two strings like '${column}' and '${dir}' as parameters and returns an Observable Array of type CreateIrisSearchRequest.`, inject([HistoryService], (service: HistoryService) => { + const fifteenA = service.getIrisSearchCreateList(start, limit, column, dir); + expect(fifteenA).toBeInstanceOf(Observable); + })); + + it(`b. getAllIrisSearchCreateList takes zero parameters and also returns an Observable Array of type CreateIrisSearchRequest.`, inject([HistoryService], (service: HistoryService) => { + const fifteenB = service.getAllIrisSearchCreateList(); + expect(fifteenB).toBeInstanceOf(Observable); + })); + + it(`c. getEbtsMessageFile takes a string like '${id}' as a parameter and returns an Observable of type Blob.`, inject([HistoryService], (service: HistoryService) => { + const fifteenC = service.getEbtsMessageFile(id); + expect(fifteenC).toBeInstanceOf(Observable); + })); + + it(`d. saveIrisSearchRequest takes an instance of the CreateIrisSearchRequest class like '${JSON.stringify(iris, undefined, 4)} as a parameter and returns an Observable of type CreateIrisSearchRequest.`, inject([HistoryService], (service: HistoryService) => { + const fifteenD = service.saveIrisSearchRequest(iris); + expect(fifteenD).toBeInstanceOf(Observable); + })); + + it(`e. getAllReviewList takes two numbers like ${start} and ${limit} and two strings like '${column}' and '${dir}' as parameters and returns an Observable Array of type ReportData.`, inject([HistoryService], (service: HistoryService) => { + const fifteenE = service.getAllReviewList(start, limit, column, dir); + expect(fifteenE).toBeInstanceOf(Observable); + })); + + it(`f. getReview takes a string like '${reviewId}' as a parameter and returns an Observable of type ReportData.`, inject([HistoryService], (service: HistoryService) => { + const fifteenF = service.getReview(reviewId); + expect(fifteenF).toBeInstanceOf(Observable); + })); + + it(`g. getSearch takes a string like '${reviewId}' as a parameter and returns an Observable of type CreateIrisSearchRequest.`, inject([HistoryService], (service: HistoryService) => { + const fifteenG = service.getSearch(reviewId); + expect(fifteenG).toBeInstanceOf(Observable); + })); + + it(`h. getSearchCount returns an Observable of type number.`, inject([HistoryService], (service: HistoryService) => { + const fifteenH = service.getSearchCount(); + expect(fifteenH).toBeInstanceOf(Observable); + })); + + it(`i. getReviewCount returns an Observable of type number.`, inject([HistoryService], (service: HistoryService) => { + const fifteenI = service.getReviewCount(); + expect(fifteenI).toBeInstanceOf(Observable); + })); + +}); diff --git a/web/client/src/app/history/history.service.ts b/web/client/src/app/history/history.service.ts new file mode 100644 index 0000000..b7b3d10 --- /dev/null +++ b/web/client/src/app/history/history.service.ts @@ -0,0 +1,203 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable, EventEmitter, Output, Directive } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { CreateIrisSearchRequest } from '../types/create-iris-search-request'; +import { ReportData } from '../types/report-data'; +import { State } from '../types/state'; +import { MapUtils } from '../utils/map-utils'; + +@Directive() +@Injectable({providedIn: 'root'}) +export class HistoryService { + getSearchRequestsUrl = '/api/iris/search/list'; + getEbtsMessageFileUrl = '/api/iris/search/download'; + saveSearchRequestUrl = '/api/iris/search/save'; + getSearchUrl = '/api/iris/search/find/'; + getReviewUrl = '/api/iris/report/find/'; + getReviewListUrl = '/api/iris/report/list'; + saveReviewUrl = '/api/iris/report/save'; + getSearchCountUrl = '/api/iris/search/count'; + getReviewCountUrl = '/api/iris/report/count'; + ebtsURL = '/api/iris/search/ebts'; + + // need to notify the navbar of updates + // This is the child component + @Output() modifiedNotifications: EventEmitter = new EventEmitter(); + + notificationsModified(str) { + this.modifiedNotifications.emit(str); + } + + _savedIrisSearchRequest: CreateIrisSearchRequest; + _hasIrisSearchRequest = false; + + get hasIrisSearchRequest() { + return this._hasIrisSearchRequest; + } + + set savedIrisSearchRequest(val: CreateIrisSearchRequest) { + this._savedIrisSearchRequest = val; + this._hasIrisSearchRequest = true; + } + + get savedIrisSearchRequest() { + return this._savedIrisSearchRequest; + } + + + _savedReview: ReportData; + _hasSavedReview = false; + + get hasSavedReview() { + return this._hasSavedReview; + } + + set savedReview(val: ReportData) { + this._savedReview = val; + this._hasSavedReview = true; + } + + get savedReview() { + return this._savedReview; + } + + constructor( + private http: HttpClient + ) { + } + + getIrisSearchCreateList(start: number, limit: number, column?: string, dir?: string): Observable> { + let creates; + if (!dir) { +dir = 'desc'; +} + if (!column) { + creates = this.http.get>(this.getSearchRequestsUrl, { + params: new HttpParams() + .set('start', start.toString()) + .set('limit', limit.toString()) + .set('direction', dir) + }); + } else { + creates = this.http.get>(this.getSearchRequestsUrl, { + params: new HttpParams() + .set('start', start.toString()) + .set('limit', limit.toString()) + .set('sort', column) + .set('direction', dir) + }); + } + + return creates.pipe(map((data: CreateIrisSearchRequest[]) => data.map(val => MapUtils.deserialize(CreateIrisSearchRequest, val)))); + } + + getAllIrisSearchCreateList(): Observable> { + return this.http.get>(this.getSearchRequestsUrl).pipe( + map( + (data: CreateIrisSearchRequest[]) => data.map( + val => MapUtils.deserialize(CreateIrisSearchRequest, val)! + ) + ) + ); + } + + getEbtsMessageFile(id: string): Observable { + return this.http.get(this.getEbtsMessageFileUrl, { + params: new HttpParams().set('id', id) + }); + } + + saveIrisSearchRequest(createIrisSearchRequest: CreateIrisSearchRequest): Observable { + return this.http.post(this.saveSearchRequestUrl, createIrisSearchRequest); + } + + getAllReviewList(start?: number, limit?: number, column?: string, dir?: string): Observable> { + const tempStart = start || 0; + let reviews; + + if( !column ) { + column = 'caseInformation.comparisonDate'; + } + if( !dir ) { + dir = 'desc'; + } + + if( limit ) { + reviews = this.http.get>( this.getReviewListUrl, { + params: new HttpParams() + .set('start', start!.toString()) + .set('limit', limit.toString()) + .set('direction', dir) + .set('column', column) + }); + + } else { + reviews = this.http.get>(this.getReviewListUrl, { + params: new HttpParams() + .set('start', start!.toString()) + .set('direction', dir) + .set('column', column) + }); + } + + return reviews.pipe(map((data: ReportData[]) => data.map(val => MapUtils.deserialize(ReportData, val)))); + } + + getReview(reviewId): Observable { + return this.http.get(this.getReviewUrl + reviewId).pipe(map( + res => MapUtils.deserialize(ReportData, res)!)); + } + + getSearch(searchId): Observable { + return this.http.get(this.getSearchUrl + searchId).pipe(map( + search => MapUtils.deserialize(CreateIrisSearchRequest, search)!)); + } + + saveReview(reportData: ReportData): Observable { + // Remove pastStates/currState to get rid of cyclic JSON: + + for (const image of reportData.probe.imageList) { + image.pastStates = new Array(); + image.currState = new State(); + } + + for (const candidate of reportData.candidates) { + for (const cimage of candidate.imageList) { + cimage.pastStates = new Array(); + cimage.currState = new State(); + } + } + + return this.http.post(this.saveReviewUrl, reportData); + } + + getSearchCount(): Observable { + return this.http.get(this.getSearchCountUrl); + } + + getReviewCount(): Observable { + return this.http.get(this.getReviewCountUrl); + } + + clearSavedReview() { + this.savedReview = new ReportData(); + this._hasSavedReview = false; + } + + clearIrisSearchRequest() { + this.savedIrisSearchRequest = new CreateIrisSearchRequest(); + this._hasIrisSearchRequest = false; + } +} diff --git a/web/client/src/app/history/user-notification.ts b/web/client/src/app/history/user-notification.ts new file mode 100644 index 0000000..95c2a3a --- /dev/null +++ b/web/client/src/app/history/user-notification.ts @@ -0,0 +1,20 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +export class UserNotification { + id = ''; + title = ''; + message = ''; + receivedDate = ''; + status = ''; + filePath = ''; +} + + diff --git a/web/client/src/app/home/home.component.html b/web/client/src/app/home/home.component.html new file mode 100644 index 0000000..fca1610 --- /dev/null +++ b/web/client/src/app/home/home.component.html @@ -0,0 +1,71 @@ + +
+
+
+
+

Welcome

+
+
+
+
+

This is the Iris Analysis Toolkit, an application to search and review iris + images.

+
+
+
+ + +
+ +
+ + Create EBTS + +
+ +

Create an iris EBTS search file.

+
+
+ + +
+ +
+ + Review + +
+ +

Review and analyze iris imagery from uploaded images or EBTS files.

+
+
+ + +
+ +
+ + Manage + +
+ +

Manage information associated with Contributors to Iris Submissions and Search requests.

+
+
+
+
+
diff --git a/web/client/src/app/home/home.component.ts b/web/client/src/app/home/home.component.ts new file mode 100644 index 0000000..24feb87 --- /dev/null +++ b/web/client/src/app/home/home.component.ts @@ -0,0 +1,66 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component, OnInit } from '@angular/core'; +import { ProfileService } from '../profile/profile.service'; +import { Profile } from '../profile/profile'; + +@Component({ + templateUrl: 'home.component.html', + styles: [` + .card-grid { + display: grid; + grid-auto-flow: column; + grid-gap: 15px; + } + + .fab-icon { + margin-top: -2px; + } + + .title-link { + font-size: 27px; + cursor: pointer; + } + + + `] +}) + +export class HomeComponent implements OnInit { + + prefs: Profile; + privs: any = {}; + + + constructor( + private profileService: ProfileService + ) { + this.profileService.getUserPrivileges().subscribe(privs => { + this.privs = privs; + }); + } + + ngOnInit() { + this.profileService.getProfile().subscribe(prfs => { + this.prefs = prfs; + this.loadUserData(); + }); + } + + loadUserData() { + + } + + handleTableChanges() { + this.loadUserData(); + } + +} diff --git a/web/client/src/app/iris-display/core/core-functions.ts b/web/client/src/app/iris-display/core/core-functions.ts new file mode 100644 index 0000000..8ed8479 --- /dev/null +++ b/web/client/src/app/iris-display/core/core-functions.ts @@ -0,0 +1,893 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Annotation, AnnotationType } from '../../types/annotation'; +import { Path, Point, Size, Rectangle, Raster, Color, PointText } from 'paper'; + +let globalRotationValue = 0; +export enum ColorChannel { + RED, + GREEN, + BLUE +} + +export class ColorChannelAddition { + red: number; + green: number; + blue: number; +} + +export class CoreFunctions { + + drawCircle(point: InstanceType, radius: number, strokeColor: string, fillColor: string, strokeWidth?: number): InstanceType { + + if (fillColor == undefined) { + fillColor = 'rgba(0, 0, 0, 0)'; + } + + const shape = new Path.Circle({ + center: point, + radius + }); + shape.strokeColor = new Color(strokeColor); + shape.fillColor = new Color(fillColor); + + if (strokeWidth) { + shape.strokeWidth = strokeWidth; + } + + return shape; + } + + drawSquare(point: InstanceType, radius: number, strokeColor: string, fillColor: string): InstanceType { + const shape = new Path.Rectangle(point, new Size(radius, radius)); + shape.strokeColor = new Color(strokeColor); + shape.fillColor = new Color(fillColor); + + return shape; + } + + clahe(imageData: ImageData, tileDivisor: number, clipFactor: number): ImageData { + + const imageWidth = imageData.width; + const imageHeight = imageData.height; + const kernelSize = {x: Math.floor(imageWidth / tileDivisor), y: Math.floor(imageHeight / tileDivisor)}; + const clipLimit = Math.floor(clipFactor * kernelSize.x * kernelSize.y); + const middleKernelSizeX = Math.floor(kernelSize.x / 2); + const middleKernelSizeY = Math.floor(kernelSize.y / 2); + const numberOfTilesX = Math.floor(imageWidth / kernelSize.x); + const numberOfTilesY = Math.floor(imageHeight / kernelSize.y); + + const grayImage = this.grayScaleImage(imageData); + + const getImagePosition = (x, y) => x + y * imageWidth; + + const hist = new Array>(); + + for (let y = 0; y < imageHeight; y += kernelSize.y) { + for (let x = 0; x < imageWidth; x += kernelSize.x) { + const temp = new Array(256); + for (let t = 0; t < temp.length; t++) { + temp[t] = 0; + } + + for (let j = 0; j < kernelSize.y; j++) { + for (let i = 0; i < kernelSize.x; i++) { + const intensity = grayImage[getImagePosition(x + i, y + j)]; + temp[intensity] += 1; + } + }//end of through kernel loop + + hist.push(temp); + }//end of for x + }//end of for y + + //clipping the amount on the histograms + for (const h of hist) { + let excess = 0; + //check each intensity to make sure it doesn't go past the limit + for (let i = 0; i < 256; i++) { + if (h[i] > clipLimit) { + excess += h[i] - clipLimit; + h[i] = clipLimit; + } + }//end of for + + if (excess > 0) { + const additionalAmt = excess / 256; + for (let i = 0; i < 256; i++) { + h[i] += additionalAmt; + } + } + + } + + const cdf = new Array>(); + for (const h of hist) { + const temp = new Array(h.length); + temp[0] = h[0] / (kernelSize.x * kernelSize.y); + + for (let t = 1; t < h.length; t++) { + temp[t] = temp[t - 1] + (h[t] / (kernelSize.x * kernelSize.y)); + }//end of for + + cdf.push(temp); + }//end of for + + //try to do bileear interpolation + //https://en.wikipedia.org/wiki/Bilinear_interpolation <---came from here + //for each image point you are trying to find four tile center points that + //surround the point with point1 being top left, point2 being top right, + //point3 bottom right, and point4 bottom left. You then take a bilinear interpolation + //of those points to figure out the intensity of the image point. To paraphrase you + //are using the surrounding tile's histograms to come up with the intensity of + //a point which is an approximation of taking a point's histogram and cdf and + //using that to get the intensity of a point which would be to computationally + //intensive to use. + for (let y = middleKernelSizeY; y < imageHeight - (middleKernelSizeY); y++) { + for (let x = middleKernelSizeX; x < imageWidth - (middleKernelSizeX); x++) { + //this is the tile that it is in + const cy = Math.floor(y / kernelSize.y); + const cx = Math.floor(x / kernelSize.x); + + //check if it is the middle point in the tile + const tempx = x % kernelSize.x; + const tempy = y % kernelSize.y; + if (tempx == middleKernelSizeX && tempy == middleKernelSizeY) { + const intense = grayImage[getImagePosition(x, y)]; + grayImage[getImagePosition(x, y)] = cdf[Math.floor(cx + (cy * (numberOfTilesX)))][intense] * 255; + continue; + } + + const point1 = {x: 0, y: 0, cx: 0, cy: 0}; + const point2 = {x: 0, y: 0, cx: 0, cy: 0}; + const point3 = {x: 0, y: 0, cx: 0, cy: 0}; + const point4 = {x: 0, y: 0, cx: 0, cy: 0}; + + //get the four closest middle tile points + if (tempx < middleKernelSizeX && tempy < middleKernelSizeY) { + //left top quadrant of tile + point1.x = (cx - 1) * kernelSize.x + Math.floor(middleKernelSizeX); + point1.y = (cy - 1) * kernelSize.y + Math.floor(middleKernelSizeY); + point1.cx = cx - 1; + point1.cy = cy - 1; + + point2.x = (cx) * kernelSize.x + Math.floor(middleKernelSizeX); + point2.y = (cy - 1) * kernelSize.y + Math.floor(middleKernelSizeY); + point2.cx = cx; + point2.cy = cy - 1; + + point3.x = (cx) * kernelSize.x + Math.floor(middleKernelSizeX); + point3.y = (cy) * kernelSize.y + Math.floor(middleKernelSizeY); + point3.cx = cx; + point3.cy = cy; + + point4.x = (cx - 1) * kernelSize.x + Math.floor(middleKernelSizeX); + point4.y = (cy) * kernelSize.y + Math.floor(middleKernelSizeY); + point4.cx = cx - 1; + point4.cy = cy; + + } else if (tempx >= middleKernelSizeX && tempy < middleKernelSizeY) { + //right top quadrant of tile + point1.x = (cx) * kernelSize.x + Math.floor(middleKernelSizeX); + point1.y = (cy - 1) * kernelSize.y + Math.floor(middleKernelSizeY); + point1.cx = cx; + point1.cy = cy - 1; + + point2.x = (cx + 1) * kernelSize.x + Math.floor(middleKernelSizeX); + point2.y = (cy - 1) * kernelSize.y + Math.floor(middleKernelSizeY); + point2.cx = cx + 1; + point2.cy = cy - 1; + + point3.x = (cx + 1) * kernelSize.x + Math.floor(middleKernelSizeX); + point3.y = (cy) * kernelSize.y + Math.floor(middleKernelSizeY); + point3.cx = cx + 1; + point3.cy = cy; + + point4.x = (cx) * kernelSize.x + Math.floor(middleKernelSizeX); + point4.y = (cy) * kernelSize.y + Math.floor(middleKernelSizeY); + point4.cx = cx; + point4.cy = cy; + + } else if (tempx >= middleKernelSizeX && tempy >= middleKernelSizeY) { + //right bottom quadrant of tile + point1.x = (cx) * kernelSize.x + Math.floor(middleKernelSizeX); + point1.y = (cy) * kernelSize.y + Math.floor(middleKernelSizeY); + point1.cx = cx; + point1.cy = cy; + + point2.x = (cx + 1) * kernelSize.x + Math.floor(middleKernelSizeX); + point2.y = (cy) * kernelSize.y + Math.floor(middleKernelSizeY); + point2.cx = cx + 1; + point2.cy = cy; + + point3.x = (cx + 1) * kernelSize.x + Math.floor(middleKernelSizeX); + point3.y = (cy + 1) * kernelSize.y + Math.floor(middleKernelSizeY); + point3.cx = cx + 1; + point3.cy = cy + 1; + + point4.x = (cx) * kernelSize.x + Math.floor(middleKernelSizeX); + point4.y = (cy + 1) * kernelSize.y + Math.floor(middleKernelSizeY); + point4.cx = cx; + point4.cy = cy + 1; + + } else if (tempx < middleKernelSizeX && tempy >= middleKernelSizeY) { + //left bottom quadrant of tile + point1.x = (cx - 1) * kernelSize.x + Math.floor(middleKernelSizeX); + point1.y = (cy) * kernelSize.y + Math.floor(middleKernelSizeY); + point1.cx = cx - 1; + point1.cy = cy; + + point2.x = (cx) * kernelSize.x + Math.floor(middleKernelSizeX); + point2.y = (cy) * kernelSize.y + Math.floor(middleKernelSizeY); + point2.cx = cx; + point2.cy = cy; + + point3.x = (cx) * kernelSize.x + Math.floor(middleKernelSizeX); + point3.y = (cy + 1) * kernelSize.y + Math.floor(middleKernelSizeY); + point3.cx = cx; + point3.cy = cy + 1; + + point4.x = (cx - 1) * kernelSize.x + Math.floor(middleKernelSizeX); + point4.y = (cy + 1) * kernelSize.y + Math.floor(middleKernelSizeY); + point4.cx = cx - 1; + point4.cy = cy + 1; + + } + + const intensity = grayImage[getImagePosition(x, y)]; + const fq11 = cdf[Math.floor((point1.cx) + ((point1.cy) * (numberOfTilesX)))][intensity] * 255; + const fq21 = cdf[Math.floor((point2.cx) + ((point2.cy) * (numberOfTilesX)))][intensity] * 255; + const fq12 = cdf[Math.floor((point4.cx) + ((point4.cy) * (numberOfTilesX)))][intensity] * 255; + const fq22 = cdf[Math.floor((point3.cx) + ((point3.cy) * (numberOfTilesX)))][intensity] * 255; + + const fxy1 = ((point2.x - x) / (point2.x - point1.x)) * fq11 + ((x - point1.x) / (point2.x - point1.x)) * fq21; + const fxy2 = ((point3.x - x) / (point3.x - point4.x)) * fq12 + ((x - point4.x) / (point3.x - point4.x)) * fq22; + + const fxy = ((point3.y - y) / (point3.y - point1.y)) * fxy1 + ((y - point1.y) / (point3.y - point1.y)) * fxy2; + + grayImage[getImagePosition(x, y)] = fxy; + + } + } + + //do the transfrom for the four corners + //the transformation function is the histogram equalization function for the tile + //which is intensity = cdf(intensity) * maximum gray level + for (let y = 0; y < middleKernelSizeY; y++) { + for (let x = 0; x < middleKernelSizeX; x++) { + //upper left + let intensity = grayImage[getImagePosition(x, y)]; + grayImage[getImagePosition(x, y)] = cdf[0][intensity] * 255; + + //upper right + const upperRightcx = Math.floor(numberOfTilesX - 1); + const upperRightcy = 0; + intensity = grayImage[getImagePosition(x + upperRightcx * kernelSize.x + (middleKernelSizeX), y)]; + grayImage[getImagePosition(x + upperRightcx * kernelSize.x + (middleKernelSizeX), y)] = cdf[upperRightcx + (upperRightcy * (numberOfTilesX))][intensity] * 255; + + //bottom left + const lowerLeftcx = 0; + const lowerLeftcy = Math.floor(numberOfTilesY - 1); + intensity = grayImage[getImagePosition(x, y + lowerLeftcy * kernelSize.y)]; + grayImage[getImagePosition(x, y + lowerLeftcy * kernelSize.y + (middleKernelSizeY))] = cdf[lowerLeftcx + (lowerLeftcy * (numberOfTilesX))][intensity] * 255; + + //bottom right + const lowerRightcx = Math.floor(numberOfTilesX - 1); + const lowerRightcy = Math.floor(numberOfTilesY - 1); + intensity = grayImage[getImagePosition(x + lowerRightcx * kernelSize.x + (middleKernelSizeX), y + lowerRightcy * kernelSize.y + (middleKernelSizeY))]; + grayImage[getImagePosition(x + lowerRightcx * kernelSize.x + (middleKernelSizeX), y + lowerRightcy * kernelSize.y + (middleKernelSizeY))] = cdf[lowerRightcx + (lowerRightcy * (numberOfTilesX))][intensity] * 255; + } + } + + //do the bars on the side and use linear interpolation + //Comes from https://en.wikipedia.org/wiki/Linear_interpolation + //y = (y0 * (x1 - x) + y1 * (x - x0) ) / (x1 - x0) = y0 * (x1-x)/(x1-x0) + y1 * (x-x0)/(x1-x0) + //much like above in the bilinear interpolation + //you don't have to worry about the other variable for the point (x,y) since they are the same + //for this example what you are worried about is between (x, f(x, y)) with y being a constant + //below for the right left bar x is the constant and y is the variable i.e. (y, f(x,y)) is what + //you are worried about + //top bar + for (let y = 0; y < middleKernelSizeY; y++) { + for (let x = middleKernelSizeX; x < imageWidth - (middleKernelSizeX); x++) { + const cx = Math.floor(x / kernelSize.x); + + const point1 = {x: 0, y: 0, cx: 0, cy: 0}; + const point2 = {x: 0, y: 0, cx: 0, cy: 0}; + if (x % kernelSize.x < (middleKernelSizeX)) { + //on the left side of the current tile half i.e. upper left + point1.x = (cx - 1) * kernelSize.x + (middleKernelSizeX); + point1.y = middleKernelSizeY; + point1.cx = cx - 1; + point1.cy = 0; + + point2.x = (cx) * kernelSize.x + (middleKernelSizeX); + point2.y = middleKernelSizeY; + point2.cx = cx; + point2.cy = 0; + } else { + //on the right side of the current tile half i.e. upper right + point1.x = (cx) * kernelSize.x + (middleKernelSizeX); + point1.y = middleKernelSizeY; + point1.cx = cx; + point1.cy = 0; + + point2.x = (cx + 1) * kernelSize.x + (middleKernelSizeX); + point2.y = middleKernelSizeY; + point2.cx = cx + 1; + point2.cy = 0; + } + + const intensity = grayImage[getImagePosition(x, y)]; + const fq11 = cdf[Math.floor((point1.cx) + ((point1.cy) * (numberOfTilesX)))][intensity] * 255; + const fq21 = cdf[Math.floor((point2.cx) + ((point2.cy) * (numberOfTilesX)))][intensity] * 255; + const fxy1 = ((point2.x - x) / (point2.x - point1.x)) * fq11 + ((x - point1.x) / (point2.x - point1.x)) * fq21; + grayImage[getImagePosition(x, y)] = fxy1; + } + } + + //bottom bar + for (let y = imageHeight - (middleKernelSizeY); y < imageHeight; y++) { + for (let x = middleKernelSizeX; x < imageWidth - (middleKernelSizeX); x++) { + const cx = Math.floor(x / kernelSize.x); + const cy = Math.floor(numberOfTilesY - 1); + + const point1 = {x: 0, y: 0, cx: 0, cy: 0}; + const point2 = {x: 0, y: 0, cx: 0, cy: 0}; + if (x % kernelSize.x < (middleKernelSizeX)) { + //on the left side of the current tile half i.e. upper left + point1.x = (cx - 1) * kernelSize.x + (middleKernelSizeX); + point1.y = middleKernelSizeY; + point1.cx = cx - 1; + point1.cy = cy; + + point2.x = (cx) * kernelSize.x + (middleKernelSizeX); + point2.y = middleKernelSizeY; + point2.cx = cx; + point2.cy = cy; + } else { + //on the right side of the current tile half i.e. upper right + point1.x = (cx) * kernelSize.x + (middleKernelSizeX); + point1.y = middleKernelSizeY; + point1.cx = cx; + point1.cy = cy; + + point2.x = (cx + 1) * kernelSize.x + (middleKernelSizeX); + point2.y = middleKernelSizeY; + point2.cx = cx + 1; + point2.cy = cy; + } + + const intensity = grayImage[getImagePosition(x, y)]; + const fq11 = cdf[Math.floor((point1.cx) + ((point1.cy) * (numberOfTilesX)))][intensity] * 255; + const fq21 = cdf[Math.floor((point2.cx) + ((point2.cy) * (numberOfTilesX)))][intensity] * 255; + const fxy1 = ((point2.x - x) / (point2.x - point1.x)) * fq11 + ((x - point1.x) / (point2.x - point1.x)) * fq21; + grayImage[getImagePosition(x, y)] = fxy1; + } + } + + //left bar + for (let y = middleKernelSizeY; y < imageHeight - (middleKernelSizeY); y++) { + for (let x = 0; x < (middleKernelSizeX); x++) { + const cx = 0; + const cy = Math.floor(y / kernelSize.y); + + const point1 = {x: 0, y: 0, cx: 0, cy: 0}; + const point2 = {x: 0, y: 0, cx: 0, cy: 0}; + if (y % kernelSize.y < (middleKernelSizeY)) { + //the point is top left + point1.x = middleKernelSizeX; + point1.y = (cy - 1) * kernelSize.y + (middleKernelSizeY); + point1.cx = cx; + point1.cy = cy - 1; + + point2.x = middleKernelSizeX; + point2.y = (cy) * kernelSize.y + (middleKernelSizeY); + point2.cx = cx; + point2.cy = cy; + } else { + //the point is bottom left + point1.x = middleKernelSizeX; + point1.y = (cy) * kernelSize.y + (middleKernelSizeY); + point1.cx = cx; + point1.cy = cy; + + point2.x = middleKernelSizeX; + point2.y = (cy + 1) * kernelSize.y + (middleKernelSizeY); + point2.cx = cx; + point2.cy = cy + 1; + } + + const intensity = grayImage[getImagePosition(x, y)]; + const fq11 = cdf[Math.floor((point1.cx) + ((point1.cy) * (numberOfTilesX)))][intensity] * 255; + const fq21 = cdf[Math.floor((point2.cx) + ((point2.cy) * (numberOfTilesX)))][intensity] * 255; + const fxy1 = ((point2.y - y) / (point2.y - point1.y)) * fq11 + ((y - point1.y) / (point2.y - point1.y)) * fq21; + grayImage[getImagePosition(x, y)] = fxy1; + } + } + + //right bar + for (let y = middleKernelSizeY; y < imageHeight - (middleKernelSizeY); y++) { + for (let x = imageWidth - (middleKernelSizeX); x < imageWidth; x++) { + const cx = Math.floor((numberOfTilesX) - 1); + const cy = Math.floor(y / kernelSize.y); + + const point1 = {x: 0, y: 0, cx: 0, cy: 0}; + const point2 = {x: 0, y: 0, cx: 0, cy: 0}; + if (y % kernelSize.y < (middleKernelSizeY)) { + //the point is top left + point1.x = middleKernelSizeX; + point1.y = (cy - 1) * kernelSize.y + (middleKernelSizeY); + point1.cx = cx; + point1.cy = cy - 1; + + point2.x = middleKernelSizeX; + point2.y = (cy) * kernelSize.y + (middleKernelSizeY); + point2.cx = cx; + point2.cy = cy; + } else { + //the point is bottom left + point1.x = middleKernelSizeX; + point1.y = (cy) * kernelSize.y + (middleKernelSizeY); + point1.cx = cx; + point1.cy = cy; + + point2.x = middleKernelSizeX; + point2.y = (cy + 1) * kernelSize.y + (middleKernelSizeY); + point2.cx = cx; + point2.cy = cy + 1; + } + + const intensity = grayImage[getImagePosition(x, y)]; + const fq11 = cdf[Math.floor((point1.cx) + ((point1.cy) * (numberOfTilesX)))][intensity] * 255; + const fq21 = cdf[Math.floor((point2.cx) + ((point2.cy) * (numberOfTilesX)))][intensity] * 255; + const fxy1 = ((point2.y - y) / (point2.y - point1.y)) * fq11 + ((y - point1.y) / (point2.y - point1.y)) * fq21; + grayImage[getImagePosition(x, y)] = fxy1; + } + } + + const ret = this.grayToRgb(grayImage); + return new ImageData(ret, imageData.width, imageData.height); + } + + grayToRgb(imageData: Uint8ClampedArray): Uint8ClampedArray { + const rgbaLength = 4; + const ret = new Uint8ClampedArray(imageData.length * rgbaLength); + + for (let x = 0; x < imageData.length; x++) { + const xret = x * rgbaLength; + ret[xret] = imageData[x]; + ret[xret + 1] = imageData[x]; + ret[xret + 2] = imageData[x]; + ret[xret + 3] = 255; + } + + return ret; + } + + //only works agains gray scale images + getHistogram(arr: Uint8ClampedArray, start: number, end: number): Array { + const ret = new Array(256); + for (let i = 0; i < ret.length; i++) { + ret[i] = 0; + } + + for (let x = start; x < end; x++) { + const intensity = arr[x]; + ret[intensity] += 1; + } + + return ret; + + } + + grayScaleImage(imageData: ImageData): Uint8ClampedArray { + //the data for the image is in rgba order + //and needs to be brought down to 8 bit grayscale + const rgbaLength = 4; + const ret = new Uint8ClampedArray(imageData.data.length / rgbaLength); + + for (let i = 0; i < imageData.data.length; i += 4) { + const r = i / 4; + ret[r] = (imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2]) / 3; + } + + return ret; + } + + adjustBrightness(imageData: ImageData, adjustment: number): ImageData { + // based on https://www.html5rocks.com/en/tutorials/canvas/imagefilters/ + const d = imageData.data; + + for (let i = 0; i < d.length; i += 4) { + d[i] = d[i] += adjustment; + d[i + 1] = d[i + 1] += adjustment; + d[i + 2] = d[i + 2] += adjustment; + } + return imageData; + } + + changeColors(color: ColorChannel, originalData: ImageData): ImageData { + const ret = new ImageData(originalData.data, originalData.width, originalData.height); + const data = ret.data; + const origData = originalData.data; + + for (let i = 0; i < data.length; i += 4) { + switch (+color) { + case ColorChannel.RED: + data[i] = origData[i]; + data[i + 1] = origData[i]; + data[i + 2] = origData[i]; + break; + case ColorChannel.GREEN: + data[i] = origData[i + 1]; + data[i + 1] = origData[i + 1]; + data[i + 2] = origData[i + 1]; + break; + case ColorChannel.BLUE: + data[i] = origData[i + 2]; + data[i + 1] = origData[i + 2]; + data[i + 2] = origData[i + 2]; + break; + default: + data[i] = origData[i]; + data[i + 1] = origData[i + 1]; + data[i + 2] = origData[i + 2]; + break; + } + } + return ret; + } + + addColor(addition: ColorChannelAddition, originalData: ImageData): ImageData { + const imageData = new ImageData(originalData.data, originalData.width, originalData.height); + const data = imageData.data; + const origData = originalData.data; + + for (let i = 0; i < data.length; i += 4) { + // red + data[i] = origData[i] + Number(addition.red); + // green + data[i + 1] = origData[i + 1] + Number(addition.green); + // blue + data[i + 2] = origData[i + 2] + Number(addition.blue); + } + + return imageData; + } + + //contrast is in the range of [-100, 100] + changeContrast(imageData: ImageData, contrast: number): ImageData { + // based on https://stackoverflow.com/questions/10521978/html5-canvas-image-contrast + const d = imageData.data; + contrast = (contrast / 100) + 1; + const intercept = 128 * (1 - contrast); + + for (let i = 0; i < d.length; i += 4) { + d[i] = d[i] * contrast + intercept; + d[i + 1] = d[i + 1] * contrast + intercept; + d[i + 2] = d[i + 2] * contrast + intercept; + } + return imageData; + } + + drawRect(from: InstanceType, to: InstanceType, strokeColor: string, strokeWidth?: number): InstanceType { + const ret = new Path.Rectangle(from, to); + ret.strokeColor = new Color(strokeColor); + ret.strokeWidth = strokeWidth ? strokeWidth : 2; + return ret; + } + + rotatePoint(point: InstanceType, angle: number, origin: InstanceType): InstanceType { + const x = point.x - origin.x; + const y = point.y - origin.y; + + let xprime = x * Math.cos(angle) - y * Math.sin(angle); + let yprime = x * Math.sin(angle) + y * Math.cos(angle); + + xprime += origin.x; + yprime += origin.y; + + return new Point(xprime, yprime); + } + + getSubRaster(x: number, y: number, width: number, height: number, raster: InstanceType, left: number, top: number): InstanceType { + let rasterRotation = raster.parent.project.view.rotation; + const tempMatrix = (raster.parent.project.view).matrix.clone(); + rasterRotation = rasterRotation * (Math.PI / 180.0); + + x = x - left; + y = y - top; + + const tempPt = this.rotatePoint(new Point(x, y), rasterRotation, raster.bounds.center); + x = tempPt.x; + y = tempPt.y; + + + if (rasterRotation != 0) { + const rotation = -rasterRotation; + + const imageData = raster.getImageData(null); + const subImageData = new ImageData(width, height); + + for (let j = y; j < y + height; j++) { + for (let i = x; i < x + width; i++) { + const pt = this.rotatePoint(new Point(i, j), rotation, raster.bounds.center); + pt.x = Math.round(pt.x); + pt.y = Math.round(pt.y); + + if ((pt.x < imageData.width && pt.y < imageData.height) && (pt.x > 0 && pt.y > 0)) { + const index = Math.floor((pt.x + (pt.y * imageData.width)) * 4); + const sIndex = Math.floor((i - x) + ((j - y) * subImageData.width)) * 4; + + subImageData.data[sIndex] = imageData.data[index]; + subImageData.data[sIndex + 1] = imageData.data[index + 1]; + subImageData.data[sIndex + 2] = imageData.data[index + 2]; + subImageData.data[sIndex + 3] = imageData.data[index + 3]; + } else { + const sIndex = Math.floor((i - x) + ((j - y) * subImageData.width)) * 4; + subImageData.data[sIndex] = 255; + subImageData.data[sIndex + 1] = 255; + subImageData.data[sIndex + 2] = 255; + subImageData.data[sIndex + 3] = 0; + + }//end of else + } + } + + const subRaster = new Raster(); + subRaster.size = new Size(width, height); + subRaster.setImageData(subImageData); + + return subRaster; + } else { + const rect = new Rectangle(x, y, width, height); + return raster.getSubRaster(rect); + } + } + + mirrorImage(raster: InstanceType): void { + raster.scale(-1, 1); + } + + drawPath(points: Array>, strokeColor: string, strokeWidth: number) { + const path = new Path(); + path.strokeColor = new Color(strokeColor); + path.strokeWidth = strokeWidth; + + for (const point of points) { + path.add(point); + } + + //smooth the line + path.simplify(); + + return path; + } + + drawPolygon(points: Array>, strokeColor: string, strokeWidth: number, fillColor: string, fillAlpha: number, closed?: boolean) { + const path = new Path(); + path.strokeColor = new Color(strokeColor); + path.strokeWidth = strokeWidth; + + if (closed == null || closed === true) { + path.fillColor = new Color(fillColor); + path.fillColor.alpha = fillAlpha; + } else { + path.fillColor = new Color(0, 0); + } + + for (const point of points) { + path.add(point); + }//end of for loop + + if (closed != null) { + path.closed = closed; + } else { + path.closed = true; + } + return path; + } + + rotate(raster: InstanceType, rotation: number) { + //raster.rotate(rotation); + const temp = (raster.parent.project.view); + temp.rotate(rotation, raster.bounds.center); + } + + resize(raster: InstanceType, newWidth: number, newHeight: number) { + raster.size = new Size(newWidth, newHeight); + } + + sharpen(sourceImage: ImageData, sharpen: number, opaque?: boolean): ImageData { + const frac = (sharpen - 1) / -4; + const weights = [ + 0, frac, 0, + frac, sharpen, frac, + 0, frac, 0 + ]; + const imageData = new ImageData(sourceImage.width, sourceImage.height); + + // based on https://www.html5rocks.com/en/tutorials/canvas/imagefilters/ + const src = sourceImage.data; + const dst = imageData.data; + + const side = Math.round(Math.sqrt(weights.length)); + const halfSide = Math.floor(side / 2); + + const sw = imageData.width; + const sh = imageData.height; + const w = sw; + const h = sh; + + const alphaFac = opaque ? 1 : 0; + + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + const sy = y; + const sx = x; + const dstOff = (y * w + x) * 4; + + let r = 0; let g = 0; let b = 0; let a = 0; + for (let cy = 0; cy < side; cy++) { + for (let cx = 0; cx < side; cx++) { + const scy = sy + cy - halfSide; + const scx = sx + cx - halfSide; + + if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { + const srcOff = (scy * sw + scx) * 4; + const wt = weights[cy * side + cx]; + r += src[srcOff] * wt; + g += src[srcOff + 1] * wt; + b += src[srcOff + 2] * wt; + a += src[srcOff + 3] * wt; + } + } + } + + dst[dstOff] = r; + dst[dstOff + 1] = g; + dst[dstOff + 2] = b; + dst[dstOff + 3] = a + alphaFac * (255 - a); + } + } + + return imageData; + } + + drawAnnotationText(point: InstanceType, textLabel: string, textColor: string, textSize: number): InstanceType { + const text = new PointText(point); // addChild returns an Item, cast to PointText + text.justification = 'center'; + text.fillColor = new Color(textColor); + text.content = textLabel; + text.fontSize = textSize; + return text; + } + + drawAnnotation(annotation: Annotation, raster: InstanceType): InstanceType { + let item; + let imageData; + + //the plus sign is to get the annotations to work since typescript is a stupid language + switch (+annotation.type) { + case AnnotationType.POINT: + case AnnotationType.CIRCLE: + const point = new Point(annotation.value.points[0].x, annotation.value.points[0].y); + item = this.drawCircle(point, + annotation.value.data['radius'], + annotation.value.data['strokeColor'], + annotation.value.data['fillColor'], + annotation.value.data['strokeWidth']); + break; + case AnnotationType.POLYGON: + item = this.drawPolygon(annotation.value.points, + annotation.value.data['strokeColor'], + annotation.value.data['strokeWidth'], + annotation.value.data['fillColor'], + annotation.value.data['fillAlpha']); + break; + case AnnotationType.EYELID: + item = this.drawPolygon(annotation.value.points, + annotation.value.data['strokeColor'], + annotation.value.data['strokeWidth'], + annotation.value.data['fillColor'], + annotation.value.data['fillAlpha'], + false); + break; + case AnnotationType.PENCIL: + item = this.drawPath(annotation.value.points, + annotation.value.data['strokeColor'], + annotation.value.data['strokeWidth']); + break; + case AnnotationType.CLAHE: + imageData = this.clahe(raster.getImageData(null), + annotation.value.data['tileDivisor'], + annotation.value.data['clipFactor']); + raster.setImageData(imageData); + break; + case AnnotationType.BRIGHTNESS: + imageData = this.adjustBrightness(raster.getImageData(null), + +annotation.value.data['adjustment']); + raster.setImageData(imageData); + break; + case AnnotationType.CONTRAST: + imageData = this.changeContrast(raster.getImageData(null), + annotation.value.data['adjustment']); + raster.setImageData(imageData); + break; + case AnnotationType.CHANNEL: + let colorChannel; + const color = annotation.value.data['channel']; + if (color == 'Red') { + colorChannel = ColorChannel.RED; + } else if (color == 'Green') { + colorChannel = ColorChannel.GREEN; + } else if (color == 'Blue') { + colorChannel = ColorChannel.BLUE; + } + imageData = this.changeColors(colorChannel, raster.getImageData(null)); + raster.setImageData(imageData); + break; + case AnnotationType.COLOR_LEVEL: + imageData = this.addColor({ + red: annotation.value.data['red'], + green: annotation.value.data['green'], + blue: annotation.value.data['blue'] + }, raster.getImageData(null)); + raster.setImageData(imageData); + break; + case AnnotationType.CROP: +// Make image normal - remove rotate + const tempMatrix1 = (raster.parent.project.view).matrix; + tempMatrix1.append(tempMatrix1.clone().invert()); + + const subraster = this.getSubRaster(annotation.value.data['fromPointX'], + annotation.value.data['fromPointY'], + annotation.value.data['width'], + annotation.value.data['height'], + raster, + annotation.value.data['boundsLeft'], + annotation.value.data['boundsTop']); + //have to remove the subraster so it doesn't appear + subraster.remove(); + + raster.size = new Size(subraster.width, subraster.height); + raster.setImageData(subraster.getImageData(null)); + + //If there is rotation involved.... + this.rotate(raster, globalRotationValue); + globalRotationValue = 0; + + break; + case AnnotationType.ROTATE: + globalRotationValue = annotation.value.data['adjustment']; + this.rotate(raster, annotation.value.data['adjustment']); + break; + case AnnotationType.SHARPEN: + imageData = this.sharpen(raster.getImageData(null), + annotation.value.data['adjustment']); + raster.setImageData(imageData); + break; + case AnnotationType.RESIZE: + this.resize(raster, annotation.value.data['width'], annotation.value.data['height']); + break; + case AnnotationType.MIRROR: + this.mirrorImage(raster); + break; + case AnnotationType.PDM: + break; + default: + break; + } + + return item; + } +} diff --git a/web/client/src/app/iris-display/iris-display.component.css b/web/client/src/app/iris-display/iris-display.component.css new file mode 100644 index 0000000..f392e28 --- /dev/null +++ b/web/client/src/app/iris-display/iris-display.component.css @@ -0,0 +1,171 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +@import 'https://fonts.googleapis.com/icon?family=Material+Icons'; + +input.color-picker { + cursor: pointer +} + +.unroll-canvas { + position: relative; +} + +.unroll-canvas .while-loading { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 50; +} + +.fill-space { + flex: 1 1 auto; +} + +.canvas-wrapper { + overflow: hidden; +} + + +.toolbar { + float: left; +} + +.toolbar-inner-left.vertical, +.toolbar-inner-right.vertical { + display: inline-block; +} + +.toolbar.vertical { + display: flex; +} + +.canvas-div { + overflow: hidden; +} + +.tool-btn { + display: block; + box-shadow: 2px 2px 2px #888; + margin-bottom: 3px; +} + +.pallet-option-group { + overflow: hidden; +} + +.pallet-option.tool-pallet { + overflow: hidden; +} + +.tool-pallet { + height: 62px; +} + +.generic-tool { + border-radius: 4px; + border: 1px solid grey; + display: block; + height: 31px; + box-shadow: 1px 1px 1px #888; + margin-bottom: 3px; + margin-right: 1px; +} + +.color-picker { + background-color: lightgray; + width: 36px; +} + +input[type=number]::-webkit-inner-spin-button, +input[type=number]::-webkit-outer-spin-button { + opacity: 1; +} + +.divider-lg { + border-right-width: 5px; +} + +.draw-width-selector { + background-color: white; + width: 35px; + text-align: center; +} + +.pull-original-indicator, +.pull-unrolled-indicator { + display: block; + position: relative; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + text-align: center; + background-color: white; + border: 1px solid #ccc; + cursor: ns-resize; +} + +#wrapper { + padding: 16px; + min-height: 100%; + height: 100%; + box-sizing: border-box; /*new*/ +} + +mat-sidenav { + width: 250px; +} + +.icon-btn { + min-width: 32px; + width: 38px; + padding: 0px; +} + +.toolbar-btn { + height: 38px; +} + +.unrolled-label { + font-size: 14px; + font-weight: 500; +} + +.canvas-toolbar { + height: 48px; +} + +.tshepii-hints { + font-size: 12px; +} + +.group-select { + border: 1px solid #ededed; + text-align: right; + padding: 3px; +} + +mat-drawer { + width: 345px; +} + +.confirm-cancel-btn-group { + float: right; +} + +.mat-button-toggle { + display: flex; + align-items: center; +} + +.mat-button-toggle-checked { + background-color: #3F51B5; +} \ No newline at end of file diff --git a/web/client/src/app/iris-display/iris-display.component.html b/web/client/src/app/iris-display/iris-display.component.html new file mode 100644 index 0000000..e9cea3a --- /dev/null +++ b/web/client/src/app/iris-display/iris-display.component.html @@ -0,0 +1,200 @@ + + +
+ +
+
+ +
+
+ Original + Unrolled +
+ + + + + + + +
+
+ + + + + + + + + +
+ +
+ + + + + + + + + + + +
+ + +
+ + + +
+
+ + + + + + +
+
+
+ +
+
+ + +
+
+ +
+ Unrolling Iris... +

+
+
+
+
+ + +
+ +
+
+
+
+
+ Active Group: + {{toolManager.selectedAnnotation.text}} +
+
+ + + +

Image Resubmission

+ + + + {{service.displayName | titlecase}} + + + {{service.displayName | titlecase}} + + Tshepii and Annotation can only be used for Review Cases + Tshepii must + have two images + +
Select the services you want applied
+
+ + +
+
+ diff --git a/web/client/src/app/iris-display/iris-display.component.ts b/web/client/src/app/iris-display/iris-display.component.ts new file mode 100644 index 0000000..3f8b6cd --- /dev/null +++ b/web/client/src/app/iris-display/iris-display.component.ts @@ -0,0 +1,1530 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostListener, + Input, + IterableDiffer, + IterableDiffers, + OnDestroy, + OnInit, + Output, + TemplateRef, + ViewChild + } from '@angular/core'; +import { ContentService } from '../utils/content.service'; +import { ToolManagerService, UnrollStatus } from './tool-manager.service'; +import { IrisToolRegistry } from './tools/tool.registry'; +import { take } from 'rxjs/operators'; +import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; +import { Path, Point, Raster } from 'paper'; +import { CircleTool, PointTool, PolygonTool } from './tools/tool-components'; +import { Annotation, AnnotationType, ANNO_ICON_MAPPING } from '../types/annotation'; +import { LinkedMap } from '../types/linked-map'; +import { State, StateType } from '../types/state'; +import { TshepiiService } from '../tshepii/tshepii.service'; +import { IwpPoint } from '../tshepii/iwp-point'; +import { TshepiiResponse } from '../tshepii/tshepii-response'; +import { MapUtils } from '../utils/map-utils'; +import { ProfileService } from '../profile/profile.service'; +import { MatDrawer } from '@angular/material/sidenav'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { AnnotationComponent } from '../annotation-dialog/annotation.component'; +import { AnnotationHistoryComponent } from '../annotation-history-dialog/annotation-history.component'; +import { IrisToolType } from './tools/tool.base'; +import { IrisAnnotationService } from '../irisannotation/iris-annotation.service'; +import { ServiceStatus } from '../types/service-response'; +import { MatDialog } from '@angular/material/dialog'; +import { IwpServiceStatus, ServiceStatusNameType, ServiceStatusStatusType } from '../types/service-status'; +import { ServiceStatusService } from '../service-status/service-status.service'; +import { AciiService } from '../acii/acii.service'; +import { CreateContentService } from '../create/create.service'; +import { EbtsImageData } from '../types/image-data'; +import { ReviewContentService } from '../review/review.service'; +import { HistoryService } from '../history/history.service'; +import { BiqtContactService } from '../biqt-contact/biqt-contact.service'; +import { PupilDilationToolService } from '../pupil-dilation-tool/pupil-dilation-tool.service'; +import { DeformedImageData } from '../pupil-dilation-tool/pupil-dilation-response'; +import { DualPDMService } from '../dual-pdm/dual-pdm.service'; + +//https://www.npmjs.com/package/angular4-color-picker + +interface canvasConfig { + width: number; + height: number; + +} + +@Component({ + selector: 'app-iris-display', + templateUrl: 'iris-display.component.html', + styleUrls: ['./iris-display.component.css'], + providers: [ + ToolManagerService // Creates an isolated instance of ToolManagerService + ], +}) +export class IrisDisplayComponent implements OnInit, OnDestroy { + + @Input() iris: HTMLImageElement; // TypeScript doesn't like "Image" + @Input() unrollImagePath = ''; + @Input() unrollRingData: object; + @Input() displayDrawColor = '#127bdc'; + @Input() displayFillColor = '#333333'; + // These are the original annotations associated with the image. You should NEVER empty this array since this holds the annotations from the last saved point. If you want to reset the image, set the actualAnnotations to this array, and if you want to clear the image, set the actualAnnotations to empty. + @Input('annotations') originalAnnotations: Array; + @Input() tshepiiHidden: boolean; + @Input() showOriginal: BehaviorSubject; + @Input() showUnrolled: BehaviorSubject; + @Input() isVertical: BehaviorSubject; + @Input() saved: Subject; + @Input() pastStates: State[]; + @Input() currState: State; + @Input() isReview = false; + @Input() sourceName: string; + @Input() currentImageData: EbtsImageData; + @Input() loadedCandidateImage: any; + @Input() loadedProbeImage: any; + + @Output('resetIris') resetEmitter = new EventEmitter(true); + @Output('clear') clearEmitter = new EventEmitter(true); + @Output() displayInit: EventEmitter = new EventEmitter(true); + @Output('irismousemove') mouseMove: EventEmitter = new EventEmitter(true); + @Output('irisAnnotationsRequest') irisAnnotationRequest: EventEmitter = new EventEmitter(true); + @Output('updateUnroll') updateUnrollEmitter: EventEmitter = new EventEmitter(true); + + @ViewChild('annotationButton', {static: true}) annotationButton: ElementRef; + @ViewChild('originalCanvas') originalCanvas: ElementRef; + @ViewChild('unrollCanvas') unrollCanvas: ElementRef; + @ViewChild('drawer') drawer: MatDrawer; + @ViewChild('annotationComp') annotationComp: AnnotationComponent; + @ViewChild('historyComp') historyComp: AnnotationHistoryComponent; + @ViewChild('historyButton', {static: true}) historyButton: ElementRef; + @ViewChild('irisAnnotationButton', {static: true}) irisAnnotationButton: ElementRef; + + tshepiiAnnotations = []; + annotationStartX = 0; + annotationStartY = 0; + + historyStartX = 0; + historyStartY = 0; + pdmModifiedId = undefined; + allServicesErrorOrDisabled = false; + private _irisAnnotationServiceSubscription: Subscription[] = []; + + id: string; + config: any; + canvas: canvasConfig = { // Canvas Configuration + width: 0, + height: 0 + }; + + rectCanvas: canvasConfig = { // Canvas Configuration + width: 0, + height: 0, + }; + + rectHeight = 80; + Unroll = UnrollStatus; // binding to use in template logic + + tools: any[] = []; + // Need this to display the name correctly in the template + allServicesButTshepii: IwpServiceStatus[] = []; + tshepiiAndAnnotationServiceOnly: IwpServiceStatus[] = []; + + serviceSelected: IwpServiceStatus[] = []; + imageTools: any[] = []; + annotationTools: any[] = []; + iconMap: Record; + activeTool: string; + + inPdmMode: boolean = false; + private _pdmResponseSubscription: Subscription; + public pdmMapper: Map = new Map(); + + private _DualPDMResponseSubscription: Subscription; + + + // Transparent image pixel to use as ghost image on draggable + TRANSPARENT_IMG_SRC = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; + + differ: IterableDiffer; + private _manualAnnotationSubscription: Subscription; + public toolChange$: Subject = new Subject(); + // Used to know which automated annotation response to use when pulling the automated annotations. + currentAutomatedAnnotationId: string; + + public static COUNT = 0; + + private currUser; + private isVerticalValue = false; + private fromShowOriginalClick: boolean = false; + + public isToolBarActive = false; + public haveAnnotationsReturned = false; + public havePdmResultsReturned = false; + public pdmResubmit = false; + + public pdmGroupAnnotation: Annotation = undefined; + + _tshepiiResponse: TshepiiResponse; + _biqtResponseSubscription: Subscription; + _contactResponseSubscription: Subscription; + _aciiResponseSubscription: Subscription; + _automatatedAnnotationResponseSubscription: Subscription; + isLoadedReview: boolean; + + _showAnnotations = false; + + get showAnnotations() { + return this._showAnnotations; + } + + set showAnnotations(s: boolean) { + this._showAnnotations = s; + } + + get serviceStatusStatusType(): typeof ServiceStatusStatusType { + return ServiceStatusStatusType; + } + + get serviceStatusNameType(): typeof ServiceStatusNameType { + return ServiceStatusNameType; + } + + _showHistory = false; + get showHistory() { + return this._showHistory; + } + + set showHistory(s: boolean) { + this._showHistory = s; + } + + get annotations(): LinkedMap { + return this.toolManager.annotations; + } + + constructor(private contentSvc: ContentService, + public toolManager: ToolManagerService, + private toolRegistry: IrisToolRegistry, + private _differs: IterableDiffers, + private _cdr: ChangeDetectorRef, + private profileService: ProfileService, public modalService: MatDialog, + private aciiService: AciiService, + private annotationService: IrisAnnotationService, + private biqtContactService: BiqtContactService, + private tshepiiService: TshepiiService, + private dualPDMService: DualPDMService, + public createService: CreateContentService, + public reviewService: ReviewContentService, + public historyService: HistoryService, + private serviceStatusService: ServiceStatusService, + private irisAnnotationService: IrisAnnotationService, + private pdmToolService: PupilDilationToolService, + ) { + this.id = '' + IrisDisplayComponent.COUNT++; + this.tshepiiHidden = true; + const trackByFunc = (idx, item) => item.id; + this.differ = this._differs.find([]).create(trackByFunc); + } + + ngOnInit() { + this.pdmModifiedId = this.currentImageData.ebtsImageId; + this.currentAutomatedAnnotationId = this.currentImageData.ebtsImageId; + this.currUser = this.profileService.getCurrentProfile(); + this.config = this.contentSvc.config; + + // Since create page doesn't' allow automated annotations, we only need to check the status of the annotation response on a review. + if (this.isReview) { + this.checkAnnotationsResponseStatus(this.currentImageData.ebtsImageId, false, this.currentAutomatedAnnotationId); + } + + this.checkPdmResponseStatus(this.currentImageData.ebtsImageId, false, this.pdmModifiedId); + if (this.isVertical) { + this.isVertical.subscribe(v => this.isVerticalValue = v); + } + this.canvas.height = this.iris.height > this.config.maxCanvasHeight ? this.config.maxCanvasHeight : this.iris.height; + + this.rectCanvas.width = this.config.rectWidth || this.iris.width; + this.rectCanvas.height = this.config.rectHeight || this.rectHeight; + + this.tools = this.config.tools || []; + for (const t of this.tools) { + if (t.type === IrisToolType.ANNOTATION.toString()) { + this.annotationTools.push(t); + } else if (t.type === IrisToolType.IMAGE.toString()) { + this.imageTools.push(t); + } + } + + this.iconMap = this.toolRegistry.iconMap; + + this.activeTool = this.toolManager.activeTool; + this.toolManager.sourceName = this.sourceName; + + this._manualAnnotationSubscription = this.toolManager.manualAnnotation.subscribe(i => { + // sometimes manually triggering the change detection to fire is required + this._cdr.detectChanges(); // fires change detection to detect the changes made by the ngDoCheck call + }); + + this.toolManager.toolChange$.subscribe((toolName) => { + this.toolChange$.next(toolName); + }); + + this.saved.subscribe((newAnnotations) => { + const arr = []; + for (const anno of newAnnotations) { + arr.push(this.parseAnnotation(anno)); + } + + this.originalAnnotations = arr; + this.toolManager.originalAnnotations = this.originalAnnotations; + }); + + } + + toggleToolBar(val: boolean, fromDualPDM: boolean = false) { + this.isToolBarActive = val; + this.deactivateTool(fromDualPDM); + } + + getIrisModified(): boolean { + return this.toolManager.getIrisModified(); + } + + ngOnDestroy() { + this._manualAnnotationSubscription.unsubscribe(); // prevents memory leaks + this.stopAnnotationStatusPolling(); + } + + activateTool(name: string) { + //this is to account for when the user doesn't wish to finish the unroll and + //move onto another tool + + this.deactivateTool(); + + this.activeTool = name; + this.toolManager.disableTools = false; + } + + deactivateTool(fromDualPDM: boolean = false) { + if (this.activeTool != undefined) { + this.toolManager.tempToolLayer.removeChildren(); + } + this.toolManager.disableTools = true; + delete this.activeTool; + + this.inPdmMode = false; + // We don't want to deactivate it if we are just entering the dual pdm mode. + if (!fromDualPDM) { + this.reviewService._showDualPDMComparison = false; + } + } + + // Need this to set the variable fromShowOriginalClick to true so that the onCanvasInit knows whether to redraw the annotations or not. + onShowOriginalClicked(event) { + this.showOriginal.next(event.source.checked); + this.fromShowOriginalClick = true; + } + + onCanvasInit(canvas) { + this.isLoadedReview = this.historyService.hasSavedReview || this.historyService.hasIrisSearchRequest; + + this.toolManager.initManager(canvas, this.iris, this.isVertical, this.fromShowOriginalClick); + + this.displayInit.emit(); + + canvas.nativeElement.onmousemove = (event) => { + this.mouseMove.emit(new Point(event.offsetX, event.offsetY)); + }; + + // Apply the annotations from the last saved point + if (this.originalAnnotations && this.originalAnnotations.length > 0) { + + for (let i = 0; i < this.originalAnnotations.length; i++) { + if (!this.originalAnnotations[i].id) { + this.originalAnnotations[i].reassignId(); + } + const anno = this.parseAnnotation(this.originalAnnotations[i]); + + if (this.isLoadedReview && anno.text == 'Iris Annotation Service') { + this.toolManager.pullAnnoButtonClicked = true; + } + + if (anno.text == "Individual Pupil Dilation Service") { + const child = anno.children.getFirst(); + this.applyPdmImageData(child.value.data["alphaScore"]); + } else if (anno.text == "Dual Pupil Dilation Service") { + const child = anno.children.getFirst(); + this.applyDualPdmImageData(); + } else { + const [item, label, usr] = this.toolManager.drawAndLabelAnno(anno); + } + this.originalAnnotations[i] = anno; + } + + this.toolManager.originalAnnotations = this.originalAnnotations.slice(); + + // If the image is being reloaded from clicking on the Original button, don't reset the image since this will overwrite the toolManager._original image, rendering the clear useless. This is also needed for the annotation history list. + if (!this.fromShowOriginalClick) { + this.toolManager.reset(false); + } + + if (this.pastStates && this.pastStates.length) { + this.toolManager.states.past = this.pastStates; + this.pastStates.every(state => { + if (state.annotation.parentId == undefined) { + if (state.annotation.text != 'Iris Annotation Service' && state.annotation.text != "Individual Pupil Dilation Service" && state.annotation.text != "Dual Pupil Dilation Service") { + this.toolManager.userParentGroupId = state.annotation.id; + } else { + this.toolManager.userParentGroupId = undefined; + } + return true; + } else { + return false; + } + }); + } else { + if (!this.isLoadedReview) { + // Basically a workaround to existing annotations. + // We can safely iterate through `originalAnnotations` without + // recursion because grouping wasn't available before + for (const anno of this.originalAnnotations) { + const state = new State(anno, StateType.ADD); + this.toolManager.states.past.push(state); + } + this.pastStates = this.toolManager.states.past; + this.pastStates.every(state => { + if (state.annotation.parentId == undefined) { + if (state.annotation.text != 'Iris Annotation Service' && state.annotation.text != "Individual Pupil Dilation Service" && state.annotation.text != "Dual Pupil Dilation Service") { + this.toolManager.userParentGroupId = state.annotation.id; + } + return true; + } else { + return false; + } + }); + } + } + + if (this.currState && this.currState.annotation) { + this.toolManager.states.curr = this.currState; + } + } + + if (this.unrollRingData != undefined && this.unrollRingData['innerCenterX'] != undefined) { + this.toolManager.unrollStatus = UnrollStatus.INITIALIZING; + + // give the outerRing a chance to draw before performing the expensive operation of unrolling the Iris + setTimeout(() => { + const innerPoint = new Point(Number(this.unrollRingData['innerCenterX']), Number(this.unrollRingData['innerCenterY'])); + const outerPoint = new Point(Number(this.unrollRingData['outerCenterX']), Number(this.unrollRingData['outerCenterY'])); + + const innerRing = new Path.Circle(innerPoint, Number(this.unrollRingData['innerRadius'])); + const outerRing = new Path.Circle(outerPoint, Number(this.unrollRingData['outerRadius'])); + + innerRing['center'] = innerPoint; + innerRing['radius'] = Number(this.unrollRingData['innerRadius']); + innerRing.name = 'inner'; + + outerRing['center'] = outerPoint; + outerRing['radius'] = Number(this.unrollRingData['outerRadius']); + outerRing.name = 'outer'; + + this.toolManager.unrollImagePath = this.unrollImagePath; + + this.toolManager.drawUnrolledIris(innerRing, outerRing); + }, 100); + } + + // If this method is being called from clicking show/hide Original or from applying a pdm, we need to redraw the annotations and set fromShowOriginalClick back to false. + if (this.fromShowOriginalClick) { + this.toolManager.redrawAnnotations(); + this.fromShowOriginalClick = false; + } + } + + onRectCanvasInit(rectCanvas) { + this.toolManager.bindRectCanvas(rectCanvas, this.isVertical); + } + + reset_clear(isReset: boolean, isClear: boolean) { + this.currentAutomatedAnnotationId = this.currentImageData.ebtsImageId; + this.pdmModifiedId = this.currentImageData.ebtsImageId; + if (isReset && !isClear) { + this.toolManager.pullAnnoButtonClicked = false; + if (this.isLoadedReview) { + this.toolManager.selectedAnnotation = undefined; + if (!!this.toolManager.originalAnnotations && this.toolManager.originalAnnotations.length < 0) { + for (const annotation of this.toolManager.originalAnnotations) { + if (annotation.text == 'Iris Annotation Service') { + this.toolManager.pullAnnoButtonClicked = true; + break; + } + } + } + } + this.toolManager._pdmOriginal = null; + this.toolManager._dualPdmOriginal = null; + var pdmAnno = this.toolManager.reset(true); + if (pdmAnno) { + const child = pdmAnno.children.getFirst(); + this.applyPdmImageData(child.value.data["alphaScore"]); + } + } else if (!isReset && isClear) { + this.toolManager.pullAnnoButtonClicked = false; + this.toolManager._pdmOriginal = null; + this.toolManager._dualPdmOriginal = null; + + this.toolManager.clear(true); + } + + this.toolManager.hasUnrolled.pipe( + take(1) + ).subscribe(unrolled => { + if (unrolled) { + this.updateUnroll(); + } + }); + + if (!!this.toolManager.componentRef) { + this.toolManager.componentRef.resetData(); + } + } + + get hasCanvas(): boolean { + return this.toolManager.initialized; + } + + get hasUnrolled(): boolean { + return this.toolManager.hasUnrolled.getValue(); + } + + exportPNG() { + if (!this.toolManager.initialized) { + return null; + } + const ret = new Raster(this.toolManager._original); + ret.remove(); + + return ret.toDataURL().split(',')[1]; + } + + setTshepii(tshepiiResponse: TshepiiResponse, colorString: string) { + this._tshepiiResponse = tshepiiResponse; + this.tshepiiAnnotations = []; + this.drawTshepii(colorString); + } + + drawTshepii(colorString: string) { + const croppedCenter = new Point(this._tshepiiResponse.cropCenter.x, this._tshepiiResponse.cropCenter.y); + const originalCenter = new Point(this._tshepiiResponse.originalIrisCenter.x, this._tshepiiResponse.originalIrisCenter.y); + + const vectorX = croppedCenter.x - originalCenter.x; + const vectorY = croppedCenter.y - originalCenter.y; + + const ratio = (this._tshepiiResponse.originalIrisRadius / this._tshepiiResponse.cropRadius); + + const group = []; + let index = 0; + for (const crypt of this._tshepiiResponse.cryptList) { + const anno = this.createPolygonFromTshepii(crypt['pointList'], vectorX, vectorY, originalCenter, ratio, colorString); + if (anno) { + this.tshepiiAnnotations.push(anno); + this.toolManager.addItem(anno, false); + } + + group.push({id: anno.id, index: index++}); + } + + this.toolManager.groupItems({no_parent: group}, false, 'Tshepii'); + + // Unroll Image + const innerCenter = new Point(this._tshepiiResponse.originalPupilCenter.x, this._tshepiiResponse.originalPupilCenter.y); + const innerRing = new Path.Circle( + innerCenter, + this._tshepiiResponse.originalPupilRadius + ); + + innerRing['center'] = innerCenter; + innerRing['radius'] = this._tshepiiResponse.originalPupilRadius; + innerRing.name = 'inner'; + + //set the outer ring + const outerCenter = new Point(this._tshepiiResponse.originalIrisCenter.x, this._tshepiiResponse.originalIrisCenter.y); + let outerRing = new Path.Circle( + outerCenter, + this._tshepiiResponse.originalIrisRadius + ); + + outerRing = outerRing; + outerRing['center'] = outerCenter; + outerRing['radius'] = this._tshepiiResponse.originalIrisRadius; + outerRing.name = 'outer'; + + this.toolManager.drawUnrolledIris(innerRing, outerRing); + this.toolManager.redrawAnnotations(); + + } + + toggleAnnotations(event: Event) { + event.preventDefault(); + this.showHistory = !this.showHistory; + if (this.showHistory) { + this.drawer.open(); + this.toolManager.resizeWidth(); + } else { + this.drawer.close(); + setTimeout(() => this.toolManager.resizeWidth(), 1200); + } + } + + get annotationStatus(): boolean { + return this.haveAnnotationsReturned; + } + + get pdmStatus(): boolean { + return this.havePdmResultsReturned; + } + + stopAnnotationStatusPolling() { + this.irisAnnotationService.stopAnnotationStatusPolling(); + this._irisAnnotationServiceSubscription.forEach(subscription => { + subscription.unsubscribe(); + }) + } + + checkAnnotationsResponseStatus(ebtsImageId: string, isResubmit: boolean, modifiedImageId?: string): void { + if (this.serviceStatusService.isServiceDisabled(ServiceStatusNameType.annotation)) { + return; + } + + this.irisAnnotationService.requestIrisAnnotationResponse(ebtsImageId, isResubmit, modifiedImageId); + + const pendingTimer = this.createService.setPendingTimer(isResubmit, ServiceStatusNameType.annotation); + + this._automatatedAnnotationResponseSubscription = this.irisAnnotationService.annotationResponse$.subscribe( + annotationResponse => { + this.createService.clearPendingTimer(annotationResponse.status, pendingTimer); + + if (annotationResponse.status == ServiceStatus.Received) { + this.haveAnnotationsReturned = true; + this.toolManager.pullAnnoButtonClicked = false; + + // Need to remove the old Automated Annotations if there are any. + this.removeAutoAnnos(); + + } + + // If it's resubmit, we should show the annotations right away. + if (annotationResponse.isResubmit) { + this.toolManager.addIrisAnnotationResponse(annotationResponse); + } + + this._automatatedAnnotationResponseSubscription.unsubscribe(); + this.createService.showSnackBar(annotationResponse.isResubmit, false, ServiceStatusNameType.annotation); + } + ); + } + + toggleComparison(annoVisible: boolean, mouseVisible: boolean, tempVisible: boolean, tshepiiVisible: boolean) { + this.toolManager.userGroup.visible = annoVisible; + this.toolManager.labelLayer.visible = annoVisible; + this.toolManager.mouseOverLayer.visible = mouseVisible; + this.toolManager.tempToolLayer.visible = tempVisible; + this.toolManager.generatedGroup.visible = tshepiiVisible; + } + + adjustPointToCenter(pointToAdjust: IwpPoint, vectorX: number, vectorY: number, originalCenter: IwpPoint, ratio: number): IwpPoint { + const tempPoint = new IwpPoint(pointToAdjust.x - vectorX, pointToAdjust.y - vectorY); + + if (ratio == 1) { + return tempPoint; + } + + const newVectorX = (tempPoint.x - originalCenter.x) * (1 - ratio); + const newVectorY = (tempPoint.y - originalCenter.y) * (1 - ratio); + + const returnPoint = new IwpPoint(tempPoint.x - newVectorX, tempPoint.y - newVectorY); + + return returnPoint; + } + + createPolygonFromTshepii(pointList: IwpPoint[], vectorX: number, vectorY: number, originalCenter: IwpPoint, ratio: number, colorString: string): Annotation { + if (pointList.length > 1) { + const annotation = new Annotation('Polygon', AnnotationType.POLYGON, PolygonTool.icon); + annotation.isSystemGenerated = true; + const points = []; + for (const point of pointList) { + const adjustPoint = this.adjustPointToCenter(point, vectorX, vectorY, originalCenter, ratio); + points.push({x: adjustPoint.x, y: adjustPoint.y}); + } + + points.push(this.adjustPointToCenter(pointList[0], vectorX, vectorY, originalCenter, ratio)); + + annotation.value = { + points, + data: { + strokeColor: colorString, + fillColor: colorString, + strokeWidth: 1, + fillAlpha: 0.1 + } + }; + + return annotation; + } + + return undefined; + } + + createPointFromTshepii(point: IwpPoint, radius: number): Annotation { + const paperPoint = new Point(point.x, point.y); + const paperPointArray = Point[1]; + paperPointArray.push(paperPoint); + + const annotation = new Annotation('Point', AnnotationType.POINT, PointTool.icon); + annotation.isSystemGenerated = true; + annotation.value = { + points: paperPointArray, + data: { + radius, + strokeColor: this.toolManager.drawColor, + strokeWidth: 1, + fillColor: this.toolManager.fillColor + } + }; + + return annotation; + } + + createCircleFromTshepii(point: IwpPoint, radius: number): Annotation { + if (radius > 0) { + const annotation = new Annotation('Circle', AnnotationType.CIRCLE, CircleTool.icon); + annotation.isSystemGenerated = true; + annotation.value = { + points: [new Point(point.x, point.y)], + data: { + radius, + strokeColor: this.toolManager.drawColor, + strokeWidth: this.toolManager.strokeWidth + } + }; + + return annotation; + } + + return undefined; + } + + exportFlattenedPNG(isSaveCreateImage: boolean, specificToolManager?: ToolManagerService) { + // If there isn't a specific ToolManager we should use, we default to the current ToolManager (this.toolManager) + let toolManager; + if (!!specificToolManager) { + toolManager = specificToolManager; + } else { + toolManager = this.toolManager; + } + + if (!toolManager.initialized) { + return null; + } + + this.toggleComparison(true, false, false, false); + toolManager.showGhostCursor = false; + + // Flatten layers + const imageLayer = toolManager.project.layers[0].clone({insert: false, deep: true}); + + for (let i = 1; i < toolManager.project.layers.length; i++) { + const subLayer = toolManager.project.layers[i]; + if (subLayer.name == 'annotation') { + const subChildren = subLayer.children.length; + + for (let child = 0; child < subChildren; child++) { + imageLayer.addChild(subLayer.children[child].clone({insert: false, deep: true})); + } + break; + } + } + + const ret = imageLayer.rasterize(); + const url = ret.toDataURL().split(',')[1]; + + ret.remove(); + + this.toggleComparison(true, true, true, true); + toolManager.showGhostCursor = true; + + return url; + } + + exportUnrollPNG() { + if (!this.toolManager.initialized) { + return null; + } + + if (this.toolManager.unrolledRaster == null) { + return ''; + } + + const imageLayer = this.toolManager._rect.layers['unrolled-items'].clone({insert: false, deep: true}); + + for (const subLayer of this.toolManager._rect.layers) { + if (!(subLayer.name == 'ghost-cursor')) { + const subChildren = subLayer.children.length; + + for (let child = 0; child < subChildren; child++) { + const childAnno = subLayer.children[child].clone({insert: false, deep: true}); + imageLayer.addChild(childAnno); + } + } + } + + const ret = imageLayer.rasterize(this.toolManager._rect.view.resolution); + + const url = ret.toDataURL().split(',')[1]; + ret.remove(); + + return url; + + //return this.toolManager.unrolledRaster.toDataURL().split(',')[1]; + } + + exportAnnotations() { + if (!this.toolManager.initialized) { + return null; + } + + const arr = []; + for (const anno of this.annotations) { + arr.push(this.exportAnnotation(anno)); + } + + return arr; + } + + exportPastStates() { + if (!this.toolManager.initialized || + this.toolManager.states.past.length <= 0) { + return null; + } + + // PROBLEM: state.children is an Object instead of LinkedMap + const arr = []; + for (const state of this.toolManager.states.past) { + arr.push(this.exportState(state)); + } + + return arr; + } + + exportCurrState() { + if (!this.toolManager.initialized || + !this.toolManager.states.curr) { + return null; + } + + const copy = this.exportState(this.toolManager.states.curr); + return copy; + } + + changeStrokeWidth(width) { + this.toolManager.strokeWidth = width; + } + + //activate the drawing canvas when you enter the dom element + @HostListener('mouseenter', ['$event']) + onMouseEnter(e) { + if (this.toolManager.initialized) { + this.toolManager.project.activate(); + } + } + + + @HostListener('mousewheel', ['$event']) + onMousewheel(event) { + if (this.toolManager.initialized && this.toolManager.canZoomImage()) { + this.toolManager.zoom(event); + return false; + } + return true; + } + + @HostListener('DOMMouseScroll', ['$event']) + onMouseWheelFirefox(event: any) { + if (this.toolManager.initialized && this.toolManager.canZoomImage()) { + this.toolManager.zoom(event); + return false; + } + return true; + } + + @HostListener('onmousewheel', ['$event']) + onMouseWheelIE(event: any) { + if (this.toolManager.initialized && this.toolManager.canZoomImage()) { + this.toolManager.zoom(event); + return false; + } + return true; + } + + @HostListener('dragover', ['$event']) + onDragOver(event: any) { + event.preventDefault(); + return false; + } + + showAnnotationColor(isShow: boolean) { + this.toolManager.drawAnnotationLabels = isShow; + this.toolManager.redrawAnnotations(); + } + + updateUnroll() { + this.updateUnrollEmitter.emit(); + this.toolManager.unrollStatus = this.Unroll.INITIALIZING; + setTimeout(() => { + this.toolManager.drawUnrolledIris(this.toolManager.unrollInnerRing, this.toolManager.unrollOuterRing); + this.toolManager.unrollStatus = this.Unroll.RECT_DRAWN; + }, 100); + } + + private expandOriginalIndicator; + private expandOriginalStartY = 0; + private expandOriginalDown = false; + + private expandUnrolledIndicator; + private expandUnrolledStartY = 0; + private expandUnrolledDown = false; + + onExpandOriginalDrag(event) { + event.preventDefault(); + if (this.expandOriginalDown) { + const yChange = event.clientY - this.expandOriginalStartY; + + this.toolManager.adjustCanvasHeight(yChange); + this.expandOriginalStartY = event.clientY; + } + } + + onExpandOriginalDragStart(event) { + this.expandOriginalIndicator = event.srcElement; + this.expandOriginalIndicator.style.color = '#dddddd'; + this.expandOriginalStartY = event.clientY; + this.expandOriginalDown = true; + + const img = new Image(); + img.src = this.TRANSPARENT_IMG_SRC; + event.dataTransfer.setDragImage(img, 0, 0); + } + + onExpandOriginalEnd(event) { + event.preventDefault(); + this.expandOriginalDown = false; + + if (this.expandOriginalIndicator) { + this.expandOriginalIndicator.style.color = 'black'; + } + } + + onExpandUnrolledDrag(event) { + event.preventDefault(); + if (this.expandUnrolledDown) { + const yChange = event.clientY - this.expandUnrolledStartY; + + this.toolManager.adjustRectHeight(yChange); + this.expandUnrolledStartY = event.clientY; + } + } + + onExpandUnrolledDragStart(event) { + this.expandUnrolledIndicator = event.srcElement; + this.expandUnrolledIndicator.style.color = '#dddddd'; + this.expandUnrolledStartY = event.clientY; + this.expandUnrolledDown = true; + + const img = new Image(); + img.src = this.TRANSPARENT_IMG_SRC; + event.dataTransfer.setDragImage(img, 0, 0); + } + + onExpandUnrolledEnd(event) { + event.preventDefault(); + this.expandUnrolledDown = false; + + if (this.expandUnrolledIndicator) { + this.expandUnrolledIndicator.style.color = 'black'; + } + } + + undo() { + this.toolManager.undoAnnotation(); + } + + redo() { + this.toolManager.redoAnnotation(); + } + + /* + * A little sketchy way to recursively "clone" annotations. + * Basically, the problem is that we can't pass LinkedMap to + * the server because `JSON.stringify()` is unable to serialize + * circular references (caused by prev/next pointers). + * We *could* use `flatted` to serialize circular references, but + * deserializing it on the server side with Spring is a little + * more complicated than I thought... + * So, we are trying to recursively convert the LinkedMap to + * a plain old Array and store it into a dummy attribute, `tempChildren` + * and null-out the `children` attribute. + * But we need to deep clone the annotations because we don't actually + * want `children` to be null. + */ + + private exportAnnotation(anno: Annotation) { + // Iterate thru attributes of `Annotation` + const copy = Object.keys(anno) + .reduce((obj, key) => { + const arr = []; + + // If we encounter the `children` field, convert it to an Array. + // Also, recursively clone all children Annotations + if (key === 'children') { + const children = anno['children'].toList(); + + for (const child of children) { + arr.push(this.exportAnnotation(child)); + } + return { + ...obj, + tempChildren: arr + }; + } else if (key === 'tempChildren') { + // Because we set `tempChildren` when we encounter `children`, + // just skip setting the field. + return { + ...obj + }; + } + + // Set the current key and value in the clone. + return { + ...obj, + [key]: anno[key] + }; + }, {}); + return copy; + } + + private exportState(state: State) { + const self = this; + const _export = function myself(st: State): any { + const copy = Object.keys(st) + .reduce((obj, key) => { + if (key === 'annotation') { + return { + ...obj, + annotation: self.exportAnnotation(st.annotation) + }; + } + return { + ...obj, + [key]: st[key] + }; + }, {}); + return copy; + }; + + let json; + switch (state.type) { + case StateType.BULK_REMOVE: + json = _export(state); + Object.keys(json.data['revertTo']).forEach(key => { + const children = json.data['revertTo'][key]; + for (const child of children) { + child['annotation'] = this.exportAnnotation(child['annotation']); + } + }); + return json; + case StateType.RESET: + case StateType.CLEAR: + // state.data['reverTo'] contains annotation array + json = _export(state); + for (let i = 0; i < json.data['revertTo'].length; i++) { + const anno = json.data['revertTo'][i]; + json.data['revertTo'][i] = this.exportAnnotation(anno); + } + return json; + case StateType.ADD: + case StateType.REMOVE: + case StateType.GROUP_ONLY: + case StateType.GROUP_ADD: + return _export(state); + default: + return null; + } + } + + private parseAnnotation(json: any) { + const anno = MapUtils.deserialize(Annotation, json); + + const arr = []; + if (anno.tempChildren) { + for (const child of anno.tempChildren) { + arr.push({ + key: child.id, + value: this.parseAnnotation(child) + }); + } + } + + if (arr.length > 0) { + anno.children = new LinkedMap(arr); + } + anno.tempChildren = null; + + // This is here because annoIcon isn't saved to the db, + // therefore when a past review/create is pulled up, + // the icons aren't populated + if (!anno.annoIcon) { + anno.annoIcon = ANNO_ICON_MAPPING[anno.type]; + } + + return anno; + } + + pullAnnotations(modifiedImageId?: string) { + // Automatically assume that imageId is the ebtsImageId + let imageId = this.currentImageData.ebtsImageId; + // But if there is a modifiedImageId coming in, we should set the imageId to that instead. + if (!!modifiedImageId) { + imageId = modifiedImageId; + } + + this.irisAnnotationService.getIrisAnnotationsResponse(imageId, false).subscribe( + irisAnnotations => { + this.toolManager.addIrisAnnotationResponse(irisAnnotations); + } + ); + + } + + resubmitButtonClicked(modal: TemplateRef) { + this.tshepiiAndAnnotationServiceOnly = []; + this.allServicesButTshepii = []; + this.serviceSelected = []; + this.modalService.open(modal); + this.getListOfServices(); + } + + getListOfServices(): void { + let services = []; + this.allServicesButTshepii = []; + this.tshepiiAndAnnotationServiceOnly = []; + let errorOrDisabledServices = 0; + + services = this.serviceStatusService.getNonWebServices(); + services.forEach(service => { + if (service.status == this.serviceStatusStatusType.Error || service.status == this.serviceStatusStatusType.Disabled) { + errorOrDisabledServices++; + } + if (service.name != this.serviceStatusNameType.tshepii && service.name != this.serviceStatusNameType.annotation) { + this.allServicesButTshepii.push(service); + } else { + this.tshepiiAndAnnotationServiceOnly.push(service); + } + }); + if (errorOrDisabledServices == 5) { + this.allServicesErrorOrDisabled = true; + } + } + + addServiceToSelected(service: IwpServiceStatus): void { + if (this.isReview || service.name != ServiceStatusNameType.tshepii) { + if (service.status == this.serviceStatusStatusType.Online) { + const index = this.serviceSelected.findIndex(serviceAdded => serviceAdded.name == service.name); + if (index != -1) { + this.serviceSelected.splice(index, 1); + } else { + this.serviceSelected.push(service); + } + } + } + } + + resubmitImage(isReview: boolean): void { + if (!isReview) { + this.resubmitCreateImage(); + } else { + this.resubmitReviewImage(); + } + this.modalService.closeAll(); + } + + resubmitCreateImage(): void { + this.serviceSelected.forEach(service => { + let imageData = this.exportFlattenedPNG(false); + + switch (service.name) { + case ServiceStatusNameType.acii: + this.aciiService.getAciiSnapshot(imageData, this.currentImageData.ebtsImageId).subscribe((imageId) => { + let modifiedImageId = imageId; + if (modifiedImageId != "-1") { + this.createService.getAutoEyeLabel(this.currentImageData.ebtsImageId, true, modifiedImageId); + } + }) + break; + + case ServiceStatusNameType.biqt: + this.biqtContactService.getBiqtContactSnapshot(imageData, this.currentImageData.ebtsImageId).subscribe((imageId) => { + let modifiedImageId = imageId; + if (modifiedImageId != "-1") { + this.createService.getIrisImageQualityAndContact(this.currentImageData.ebtsImageId, true, modifiedImageId); + } + }); + break; + + case ServiceStatusNameType.pdm: + this.pdmToolService.getPdmSnapshot(imageData, this.currentImageData.ebtsImageId).subscribe((imageId) => { + let modifiedImageId = imageId; + if (modifiedImageId != "-1") { + this.pdmModifiedId = modifiedImageId + this.pdmResubmit = true; + this.triggerPdmMode(true); + } + }); + break; + } + }); + } + + resubmitReviewImage(): void { + this.serviceSelected.forEach(service => { + let imageData = this.exportFlattenedPNG(false); + + switch (service.name) { + case ServiceStatusNameType.acii: + this.aciiService.getAciiSnapshot(imageData, this.currentImageData.ebtsImageId).subscribe((imageId) => { + let modifiedImageId = imageId; + + if (modifiedImageId != "-1") { + this.reviewService.getEyeLabel(this.currentImageData.ebtsImageId, true, modifiedImageId); + } + }) + break; + + case ServiceStatusNameType.annotation: + this.toolManager.pullAnnoButtonClicked = true; + this.annotationService.getAnnotationSnapshot(imageData, this.currentImageData.ebtsImageId).subscribe((imageId) => { + let modifiedImageId = imageId; + + if (modifiedImageId != "-1") { + this.currentAutomatedAnnotationId = modifiedImageId; + this.checkAnnotationsResponseStatus(this.currentImageData.ebtsImageId, true, this.currentAutomatedAnnotationId); + } + }); + break; + + case ServiceStatusNameType.biqt: + this.biqtContactService.getBiqtContactSnapshot(imageData, this.currentImageData.ebtsImageId).subscribe((imageId) => { + let modifiedImageId = imageId; + if (modifiedImageId != "-1") { + this.reviewService.getIrisImageQualityAndContact(this.currentImageData.ebtsImageId, true, modifiedImageId); + } + }); + break; + + case ServiceStatusNameType.pdm: + this.pdmToolService.getPdmSnapshot(imageData, this.currentImageData.ebtsImageId).subscribe((imageId) => { + let modifiedImageId = imageId; + + if (modifiedImageId != "-1") { + this.pdmModifiedId = modifiedImageId + this.pdmResubmit = true; + this.triggerPdmMode(true); + } + }); + break; + + case ServiceStatusNameType.tshepii: + // Check to make sure that there is a probe and candidate image selected + if (this.isReview && this.loadedProbeImage.image != null && this.loadedCandidateImage.image != null) { + // Get both the probe and candidate's toolManager + const reviewSvc = (this.contentSvc); + const probeInstance = reviewSvc.cache(this.loadedProbeImage.activeUuid).instance; + const candidateInstance = reviewSvc.cache(this.loadedCandidateImage.activeUuid).instance; + + // Get the annotations that are currently applied to the probe and candidate's images + let annotations = reviewSvc.getAnnotationsFromCache(); + // Remove the Tshepii parts of the annotations + this.removeTshepii(annotations[this.loadedProbeImage.activeUuid], annotations[this.loadedCandidateImage.activeUuid], probeInstance, candidateInstance); + // We need to pull the annotations again now that we removed the Tshepii parts. + annotations = reviewSvc.getAnnotationsFromCache(); + + // Get the image data for both probe and candidate + let tempProbeData = this.exportFlattenedPNG(false, probeInstance.toolManager) + let tempCandidateData = this.exportFlattenedPNG(false, candidateInstance.toolManager) + // Submit the images with their new annotations to get their Tshepii Responses. + this.tshepiiService.getTshepiiSnapshot(tempProbeData, tempCandidateData, this.loadedProbeImage.ebtsImageId, this.loadedCandidateImage.ebtsImageId).subscribe((imageId) => { + let modifiedProbeId = imageId[0]; + let modifiedCandidateId = imageId[1]; + if (modifiedProbeId != "-1" && modifiedCandidateId != "-1") { + this.reviewService.setComparisonMode(!this.reviewService._showTshepiiComparison, this.loadedProbeImage, this.loadedCandidateImage, this.contentSvc); + if (this.reviewService._showTshepiiComparison){ + const reviewSvc = (this.contentSvc); + let annotations = reviewSvc.getAnnotationsFromCache(); + // Get the tshepii comparison response + this.reviewService.getTshepii(this.loadedProbeImage, this.loadedCandidateImage, this.contentSvc, true, annotations, modifiedProbeId, modifiedCandidateId); + } + } + }); + } + break; + } + }); + } + + removeTshepii(probeAnnos: object, candidateAnnos: object, probeInstance: any, candidateInstance: any): void { + // Remove the annotations from the viewable image + this.removeImageTshepiiAnnotations(probeAnnos, probeInstance); + this.removeImageTshepiiAnnotations(candidateAnnos, candidateInstance); + + // Remove the annotations from the review instance that gets sent back to the tshepii service + this.removeInstanceTshepiiAnnotations(probeInstance); + this.removeInstanceTshepiiAnnotations(candidateInstance); + } + + removeImageTshepiiAnnotations(annos: object, instance: any):void { + for (let i in annos) { + let anno = annos[i]; + if (anno.text == "Tshepii") { + instance.toolManager.removeTshepiiItems(); + instance.toolManager.removeTshepiiHighlights(); + instance.toolManager.removeItem(anno.id, true); + } + } + } + + removeInstanceTshepiiAnnotations(instance: any): void { + instance.tshepiiAnnotations.forEach(anno => { + instance.toolManager.removeItem(anno.id, true); + }) + } + + removeAutoAnnos(): void { + // Check if there are any automated annotations in the first place + if (this.annotations.size == 0) { + return; + } else { + for (let anno of this.annotations) { + if (anno.text == "Iris Annotation Service") { + this.toolManager.removeItem(anno.id, false); + } + } + } + + } + + cancelResubmission(): void { + this.modalService.closeAll(); + } + + triggerPdmMode(isResubmit: boolean=false): void { + this.pdmGroupAnnotation = undefined; + if (!isResubmit) { + // Disable everything while in this mode + this.toggleToolBar(true, false) + this.inPdmMode = true; + } + + if (isResubmit) { + this.havePdmResultsReturned = false + } + + if (this.serviceStatusService.isServiceDisabled(ServiceStatusNameType.pdm)) { + return; + } + + this.checkPdmResponseStatus(this.currentImageData.ebtsImageId, isResubmit, this.pdmModifiedId); + } + + // Preps getting the responses for both the individual pdm service and dual pdm service. + getPdmResponses(pdmId: string, pdmType: AnnotationType, pdmIcon: string, iconType: string, isDualPdm: boolean, alphaScore?: string) { + // Creating an annotation label for the history + var currentAppliedPdm = new Annotation(pdmId, pdmType, pdmIcon, iconType); + + var groupText = "Dual Pupil Dilation Service"; + if (!isDualPdm && !!alphaScore) { + groupText = "Individual Pupil Dilation Service"; + } + + // Check to see if there is already a deformed image applies to the canvas and remove it + var removedPdm = this.removeAppliedPdm(currentAppliedPdm, groupText); + let isPdmGroup = removedPdm[0]; + let beforeId = removedPdm[1]; + currentAppliedPdm = removedPdm[2]; + let pdmParentGroup = removedPdm[3]; + + if (!isDualPdm && !!alphaScore) { + this.applyPdmImageData(alphaScore); + } else { + this.applyDualPdmImageData(); + } + + currentAppliedPdm.isSystemGenerated = true; + + if (!isDualPdm && !!alphaScore) { + currentAppliedPdm.value = { + points: [], + data: { + alphaScore: alphaScore + } + }; + } else { + currentAppliedPdm.value = { + points: [], + data: {} + }; + } + + // Creating pdm's own group under the history tab + if (isPdmGroup) { + this.toolManager.checkingUserCreatedAnnotations(currentAppliedPdm, !currentAppliedPdm.isSystemGenerated, null, beforeId, pdmParentGroup.id, null, this.pdmResubmit); + + } else if (!this.toolManager.annotationExists(currentAppliedPdm.id)) { + const pdmGroup = []; + pdmGroup.push({id: currentAppliedPdm.id, index: 0}); + this.toolManager.addItem(currentAppliedPdm, !currentAppliedPdm.isSystemGenerated); + pdmParentGroup = this.toolManager.groupItems({no_parent:pdmGroup}, false, groupText); + } + + this.pdmGroupAnnotation = pdmParentGroup; + + } + + removeAppliedPdm(currentAppliedPdm: Annotation, groupText: string): [boolean, string, Annotation, Annotation] { + var isPdmGroup = false; + var beforeId: string = null; + var pdmParentGroup: Annotation = null; + const currentGroups = this.toolManager.getAnnotations(AnnotationType.GROUP); + currentGroups.forEach(group => { + if (group.text == groupText) { + isPdmGroup = true; + pdmParentGroup = group; + if (group.children.size > 0) { + currentAppliedPdm = group.children.getFirst(); + beforeId = this.toolManager.preparingRemoval(currentAppliedPdm)[1]; + this.toolManager.removeItem(currentAppliedPdm.id, false, false, true) + } + } + }); + return [isPdmGroup, beforeId, currentAppliedPdm, pdmParentGroup]; + } + + applyPdmImageData(alphaScore: string) { + this._pdmResponseSubscription = this.pdmToolService.getPdmResponse(this.pdmModifiedId, alphaScore).subscribe(async deformedImageBytes => { + var deformedImageResults = await this.pdmToolService.convertImageBytesToImage(deformedImageBytes); + this.setIrisData(deformedImageResults.img); + this._pdmResponseSubscription.unsubscribe(); + }); + } + + applyDualPdmImageData() { + this._DualPDMResponseSubscription = this.dualPDMService.getDualPDMResponseImage(this.pdmModifiedId).subscribe( + async (dualPDMResponseImage) => { + try { + const results = await this.dualPDMService.prepImage(dualPDMResponseImage); + this.setIrisData(results.img, true); + this._DualPDMResponseSubscription.unsubscribe(); + } catch (error) { + console.error("Error preparing and displaying image:", error); + } + }, + (error) => { + console.error("Error fetching Dual PDM Response Image:", error); + }); + } + + + setIrisData(img: HTMLImageElement, setDualPdmOriginal: boolean = false) { + this.iris = img; + // If we are just displaying a dual pdm image, don't set the _pdmOriginal. Normally, we should set it though, so the default will be true. + if (!setDualPdmOriginal) { + this.toolManager._pdmOriginal = this.iris; + } else { + this.toolManager._dualPdmOriginal = this.iris; + } + + // need to reload the canvas since it has new image data + // If it's a modified image, we only want to redraw the pdm since it compressed the image already. + if (this.currentImageData.ebtsImageId != this.pdmModifiedId) { + this.toolManager.redrawAnnotations(true); + } else { + this.toolManager.redrawAnnotations(false); + } + } + + checkPdmResponseStatus(ebtsImageId: string, isResubmit: boolean, modifiedImageId?: string): void{ + let imageId = ebtsImageId; + if (!!modifiedImageId) { + imageId = modifiedImageId; + } else { + modifiedImageId = '-1'; + } + + this.pdmToolService.startPdmResponsePolling(ebtsImageId, modifiedImageId); + const serviceStatus = ServiceStatus; + this.pdmToolService._pdmStatusSubscription[imageId] =this.pdmToolService.pdmResponseStatus$.subscribe( + data => { + switch (data) { + case serviceStatus.Error: { + this.pdmToolService.stopPdmResponsePolling(imageId); + break; + } + // If the status is receieved for both images, we can submit an alpha score to get the corresponding PDM Mapper + case serviceStatus.Received: { + this.pdmToolService.stopPdmResponsePolling(imageId); + this.setPdmRelatedChecks(ebtsImageId); + this.createService.showSnackBar(isResubmit, false, ServiceStatusNameType.pdm); + break; + } + case serviceStatus.Pending: { + break; + } + case serviceStatus.NotSent: { + break; + } + default: + console.log('Pdm: No Status Yet'); + this.pdmToolService.stopPdmResponsePolling(imageId); + break; + } + }); + } + + // Sets the booleans logic for all things related to pdm + setPdmRelatedChecks(imageId: string): void { + this.havePdmResultsReturned = true; + this.pdmResubmit = false; + + const currProbe = this.reviewService.probe; + const currCandidate = this.reviewService.candidate; + + if (currProbe.ebtsImageId == imageId) { + this.reviewService.probe.hasPdmResultsReturned = true; + } else if (currCandidate.ebtsImageId == imageId) { + this.reviewService.candidate.hasPdmResultsReturned = true; + } + } + +} diff --git a/web/client/src/app/iris-display/iris-display.module.ts b/web/client/src/app/iris-display/iris-display.module.ts new file mode 100644 index 0000000..420fe83 --- /dev/null +++ b/web/client/src/app/iris-display/iris-display.module.ts @@ -0,0 +1,91 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { UtilsModule } from '../utils/utils.module'; + +import { IrisDisplayComponent } from './iris-display.component'; +import { IrisToolComponent, ToolHostDirective } from './tools/iris-tool.component'; +import { IrisDrawingTool } from './tools/tool.base'; +import { IrisToolRegistry } from './tools/tool.registry'; +import { AnnotationHistoryComponent } from '../annotation-history-dialog/annotation-history.component'; +import { AnnotationComponent } from '../annotation-dialog/annotation.component'; + +// Angular Material +import { MatButtonModule } from '@angular/material/button'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; +import {MatSlideToggleModule} from '@angular/material/slide-toggle'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatIconModule } from '@angular/material/icon'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatToolbarModule } from '@angular/material/toolbar'; + +import { + AdaptiveContrastTool, + BrightnessTool, ChannelTool, CircleTool, ColorLevelTool, + ContrastTool, + CropTool, + MirrorTool, + PencilTool, + PointTool, PolygonTool, EyelidTool, + RotateTool, ScaleTool, SharpenTool, UnrollTool, ZoomTool + } from './tools/tool-components'; +import { FindIrisTool } from './tools/tool-components/findiris.tool'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatTableModule } from '@angular/material/table'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; +import { MatInputModule } from '@angular/material/input'; +import { MatCardModule } from '@angular/material/card'; +import { PupilDilationToolComponent } from '../pupil-dilation-tool/pupil-dilation-tool.component'; +import { DualPDMComponent } from '../dual-pdm/dual-pdm.components'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { PdmConfirmationDialogModule } from '../pdm-confirmation-dialog/pdm-confirmation-dialog.module'; + + +/** + * Feature NgModule for IrisDisplayComponent (and supporting services, components, and pipes) + */ + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + UtilsModule, + MatButtonToggleModule, + MatSlideToggleModule, + MatSidenavModule, + MatIconModule, + MatToolbarModule, + MatTabsModule, + MatButtonModule, + MatDividerModule, + MatCheckboxModule, + MatTableModule, + MatFormFieldModule, + MatSelectModule, + MatInputModule, + MatCardModule, + MatTooltipModule, + PdmConfirmationDialogModule + ], + declarations: [IrisDisplayComponent, IrisToolComponent, ToolHostDirective, IrisDrawingTool, + AnnotationHistoryComponent, AnnotationComponent, + BrightnessTool, ContrastTool, PointTool, CropTool, MirrorTool, PencilTool, RotateTool, ScaleTool, ColorLevelTool, ChannelTool, + ZoomTool, PolygonTool, EyelidTool, CircleTool, SharpenTool, UnrollTool, AdaptiveContrastTool, FindIrisTool, PupilDilationToolComponent, DualPDMComponent + ], + exports: [IrisDisplayComponent], + providers: [IrisToolRegistry] +}) +export class IrisDisplayModule { +} diff --git a/web/client/src/app/iris-display/tool-manager.service.ts b/web/client/src/app/iris-display/tool-manager.service.ts new file mode 100644 index 0000000..f398c91 --- /dev/null +++ b/web/client/src/app/iris-display/tool-manager.service.ts @@ -0,0 +1,2440 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; +import { ContentService } from '../utils/content.service'; +import { ProfileService } from '../profile/profile.service'; +import { Color, Group, Item, Layer, Path, Point, PointText, Project, Raster, Rectangle, Size } from 'paper'; +import { Paper } from '../utils/paper.service'; +import { IrisDrawingTool } from './tools/tool.base'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { Annotation, AnnotationType } from '../types/annotation'; +import { LinkedMap } from '../types/linked-map'; +import { State, StateType } from '../types/state'; +import { CoreFunctions } from './core/core-functions'; + +import { IrisAnnotationResponse } from '../irisannotation/iris-annotation-response'; + +/** + * Tool Manager CANNOT inject the ToolRegistryService, it will cause a circular dependency + */ + +export enum UnrollStatus { + UNINITIALIZED, + INITIALIZING, + RECT_DRAWN +} + +@Injectable() +export class ToolManagerService { + initialized = false; + irisModified = false; + tempToolIrisModification = false; + + _project: InstanceType; + _rect: InstanceType; + _iris: InstanceType; + _irisClone: InstanceType; + _original: HTMLImageElement; + _active: string; + _componentRef: IrisDrawingTool; + _pdmOriginal: HTMLImageElement; + _dualPdmOriginal: HTMLImageElement; + irisImageLayer: InstanceType; + sourceName: string; + + unrollInnerRing: any; + unrollOuterRing: any; + unrollImagePath = ''; + + hasSaveButtonSelected = false; + + pullAnnoButtonClicked = false; + + public drawAnnotationLabels = false; + + // Hierarchy: + // + // - ghostCursorLayer + // - mouseOverLayer + // - tempToolLayer + // - labelLayer + // - annotationLayer + // + userGroup (user-added annotations) + // + generatedGroup (tshepii-generated annotations) + // - highlightGroup + // - itemsGroup + // + ghostCursorLayer: InstanceType; + mouseOverLayer: InstanceType; + tempToolLayer: InstanceType; + labelLayer: InstanceType; + annotationLayer: InstanceType; + userGroup: InstanceType; + generatedGroup: InstanceType; + + tempFillColor: InstanceType; + tempStrokeWidth: number; + + // This is needed for the reset. These will be the last saved point. So if you load from a saved history, set this as the annotations from the loaded review. + originalAnnotations: Array; + + // These are the annotations that are applied to the image at the moment. If you clear the image, set this to empty. If you want to reset the image, set this to the original annotations. + actualAnnotations = new LinkedMap(); + itemsMap = {}; // Store every single Item (paper.js) + // according to their Annotation. + // key: Annotation.id + // value: { annotation, item } + // item may be null if not a drawn annotation + + states = { + past: [], // to undo + curr: null, // last added + future: [], // to redo + }; + + selectedAnnotation: Annotation = undefined; + hidden = new Set(); // hidden annos + previouslyFiltered = new Set(); + + coreFunctions = new CoreFunctions(); + + private _manualAnnotate = new Subject(); + public manualAnnotation = this._manualAnnotate.asObservable(); + public annotationColor = '#ff0000'; + annotationTextSize = 12; + + unrolledRaster: InstanceType; + private _unrollStatus: UnrollStatus = UnrollStatus.UNINITIALIZED; + private hasUnrolledSubject = new BehaviorSubject(false); + private showOriginalSubject = new BehaviorSubject(true); + + unroll: any = {}; + userAnnoGroup: any = []; + _userParentGroupId: string; + userParentGroup: any = []; + previousParentGroupId: string; + + minHeight = 0; + maxHeight = 2000; + private currUser; + + get hasUnrolled(): BehaviorSubject { + return this.hasUnrolledSubject; + } + + get hasOriginal(): BehaviorSubject { + return this.showOriginalSubject; + } + + _unrollGhostCursor: InstanceType; // this is for the unroll raster + ghostCursor: InstanceType; // this is the one for the main image + + setUnrollCursorVisible(visible: boolean) { + if (this._unrollGhostCursor) { + this._unrollGhostCursor.visible = visible; + } + } + + set userParentGroupId(id: string) { + this._userParentGroupId = id; + } + + get userParentGroupId(): string { + return this._userParentGroupId; + } + + + set showGhostCursor(show: boolean) { + if (this.ghostCursor) { + this.ghostCursorLayer.visible = show; + this.ghostCursorLayer.bringToFront(); + } + } + + get showGhostCursor(): boolean { + if (this.ghostCursor) { + return this.ghostCursor.visible; + } + return false; + } + + set ghostCursorPosition(pos: InstanceType) { + this.ghostCursor.bounds.center = pos; + } + + get ghostCursorPosition(): InstanceType { + return this.ghostCursor.bounds.center; + } + + // Variable to keep track if the mouse is within the window + private isInWindow = false; + private isInUnrollWindow = false; + + public disableTools = false; + + /* + * Clone the current iris image and store it in `_irisClone` + * without inserting. + */ + createClone() { + this._irisClone = >(this.iris.clone({insert: false})); + } + + /* + * Check if the canvas is zoomable. + */ + canZoomImage() { + return this.isInUnrollWindow || this.isInWindow; + } + + public drawColor = '#127bdc'; + public fillColor = '#333333'; + public strokeWidth = 2; + tempDrawColor = '#0f0'; + highlightDrawColor = '#FFEE00'; + + // Event called when a tool makes a change + public toolChange$: Subject = new Subject(); + + get componentRef(): IrisDrawingTool { + return this._componentRef; + }; + + setCompRef(ref: any) { + this._componentRef = (ref.instance); + this._componentRef.toolChange$.subscribe((toolName) => { + this.toolChange$.next(toolName); + }); + } + + get activeTool(): string { + return this._active; + } + + set activeTool(tool: string) { + this._active = tool; + } + + get project() { + return this._project; + } + + get iris() { + return this._iris; + } + + get unrollStatus(): UnrollStatus { + return this._unrollStatus; + } + + set unrollStatus(s: UnrollStatus) { + this._unrollStatus = s; + } + + get annotations(): LinkedMap { + if (!this.initialized) { + return new LinkedMap(); + } + return this.actualAnnotations; + } + + constructor(public contentSvc: ContentService, + public profileService: ProfileService, + private paperSvc: Paper + ) { + this.currUser = this.profileService.getCurrentProfile(); + const settings = this.currUser.userSetting; + + this.strokeWidth = settings.strokeWidth; + this.drawColor = settings.drawColor; + this.fillColor = settings.fillColor; + } + + public createIrisRaster(iris: HTMLImageElement) { + this.irisImageLayer.activate(); + // when this raster is added to the view it will go through the view's transformation + // matrix, but it will still be at the origin (0, 0) which is the top left of the view + // so we have to translate down by half the size. Don't add a new point as part of the + // constructor because that throws all the transformation off--just translate it. + const raster = new Raster(iris); + raster.translate(new Point(iris.width / 2, iris.height / 2)); + raster.name = 'iris-raster'; + + return raster; + }; + + setIrisModified(modified: boolean) { + this.tempToolIrisModification = modified; + } + + getIrisModified() { + return this.irisModified || this.tempToolIrisModification; + } + + resizeWidth() { + // resize the canvas element to match the size of the parent element + const rect = (this._project.view.element.parentNode).getBoundingClientRect(); + const h = this._project.view.viewSize.height; + this._project.view.viewSize = new Size(rect.width, h); + } + + /* + * Initialize the ToolManager. + * + * Things that are initialized: + * - The main canvas + * - Layers + * - Mouse event bound to the canvases + * - Loaded iris image + */ + initManager(canvas, iris, isVertical, fromShowOriginalClick = false) { + // addCanvas calls activate() + this._project = this.paperSvc.addCanvas(canvas.nativeElement, null, isVertical, true); + + // resize the canvas element to match the size of the parent element + const rect = (this._project.view.element.parentNode).getBoundingClientRect(); + this._project.view.viewSize = new Size(rect.width, rect.height); + + // create iris layer and raster instance + this.irisImageLayer = new Layer({name: 'iris'}); + this._project.addLayer(this.irisImageLayer); + + // Keep a copy for reset + if (!fromShowOriginalClick) { + this._original = iris; + } + + this.bindMouseEvents(this._project.view); + + // add a class with displays a border to the parent element (canvas elements don't support borders) + this._project.view.element.parentElement.classList.add('show-boundary'); + + this.initialized = true; + + // Initialize ghostCursorLayer + this.ghostCursorLayer = new Layer({name: 'ghost-cursor'}); + this.ghostCursor = >this.ghostCursorLayer.addChild(new Path.Circle({ + center: new Point(0, 0), + radius: 5, + strokeColor: '#000', + fillColor: '#ff0' + })); + this.ghostCursorLayer.bringToFront(); + this.ghostCursorLayer.visible = false; + + this.mouseOverLayer = new Layer({name: 'mouse-over'}); + this.tempToolLayer = new Layer({name: 'temp-tool'}); + this.labelLayer = new Layer({name: 'label-layer'}); + + this.annotationLayer = new Layer({name: 'annotation'}); + this.userGroup = new Group({name: 'user'}); + this.generatedGroup = new Group({name: 'generated'}); + this.generatedGroup.addChild(new Group({name: 'highlight'})); + this.generatedGroup.addChild(new Group({name: 'item'})); + this.annotationLayer.addChildren([this.userGroup, this.generatedGroup]); + + this.project.addLayer(this.annotationLayer); + this.project.addLayer(this.labelLayer); + this.project.addLayer(this.ghostCursorLayer); + this.project.addLayer(this.mouseOverLayer); + this.project.addLayer(this.tempToolLayer); + + this._iris = this.createIrisRaster(iris); + } + + /* + * Adjust the height of the main canvas. + */ + adjustCanvasHeight(add) { + const rect = this._project.view.viewSize; + + if ((rect.height + add) != rect.height && + (rect.height + add) > this.minHeight && + (rect.height + add) < this.maxHeight) { + this._project.view.viewSize.height = rect.height + add; + } + } + + /* + * Adjust the height of the unroll canvas. + */ + adjustRectHeight(add) { + const rect = this._rect.view.viewSize; + + if ((rect.height + add) != rect.height && + (rect.height + add) > this.minHeight && + (rect.height + add) < this.maxHeight) { + this._rect.view.viewSize.height = rect.height + add; + } + } + + /* + * Initialize and set up the unroll canvas. + */ + bindRectCanvas(rectCanvas, isVertical) { + // addCanvas calls activate() + this._rect = this.paperSvc.addCanvas(rectCanvas.nativeElement, null, isVertical); + this._rect.view.element.parentElement.classList.add('show-boundary'); + + this._rect.addLayer(new Layer({name: 'unrolled-iris'})); + this._rect.addLayer(new Layer({name: 'unrolled-items'})); + } + + lookupTable = {}; + + drawUnrolledIris(inner: InstanceType, outer: InstanceType) { + this.annotationLayer.activate(); + this.unrollInnerRing = inner; + this.unrollOuterRing = outer; + + const pixel_data = this._iris.getImageData(null).data; + + for (let x = 0; x < this._iris.width; x++) { + this.lookupTable[x] = {}; + for (let y = 0; y < this._iris.height; y++) { + this.lookupTable[x][y] = [-1, -1]; + } + } + + //put in 2d lookup array for performance + const img_data = {}; + for (let y = 0; y < this._iris.height; y++) { + img_data[y] = {}; + for (let x = 0; x < this._iris.width; x++) { + const index = (y * this._iris.width + x) * 4; + img_data[y][x] = [pixel_data[index], pixel_data[index + 1], pixel_data[index + 2]]; + } + } + + const size = this._project.layers['iris'].bounds; + const dx = this._iris.width / size.width; // relative to the img in the raster, not the current view + const dy = this._iris.height / size.height; + const px = inner['center'].x * dx; + const py = inner['center'].y * dy; + const pr = inner['radius']; + const lx = outer['center'].x * dx; + const ly = outer['center'].y * dy; + const lr = outer['radius']; + + // get the rectangle canvas + this._rect.activate(); + this._rect.layers['unrolled-iris'].activate(); // draw specific layer + this._rect.layers['unrolled-iris'].clear(); // Appears to prevent unusual sizing issues with unroll raster + + const power = 1.5; + const rate = 80; + + for (let degree = 0; degree <= 360; degree++) { + let idx = 0; + let idy = 0; + const radians = (Math.PI / 180) * degree; + const ix = px + pr * Math.cos(radians); // inner + const iy = py + pr * Math.sin(radians); + const ox = lx + lr * Math.cos(radians); // outer + const oy = ly + lr * Math.sin(radians); + + for (let r = 0; r <= rate; r++) { + idx = Math.trunc(ix + Math.pow(r, power) * (ox - ix) / (Math.pow((rate - 1), power))); + idy = Math.trunc(iy + Math.pow(r, power) * (oy - iy) / (Math.pow((rate - 1), power))); + this.lookupTable[idx][idy] = [degree, r]; + try { + if (this._iris.bounds.topLeft.x != 0) { + idx = idx - this._iris.bounds.topLeft.x; + } + + if (this._iris.bounds.topLeft.y != 0) { + idy = idy - this._iris.bounds.topLeft.y; + } + + const d = img_data[Math.floor(idy)][Math.floor(idx)]; // get from lookup, getImageData is way too slow + const rgb = 'rgb(' + d[0] + ', ' + d[1] + ', ' + d[2] + ')'; + const rectangle = new Rectangle(new Point(degree, r), new Size(1, 1)); + const path = new Path.Rectangle(rectangle); + path.fillColor = new Color(rgb); + } catch (e) { + console.log(e); + console.log('idx:', idx, 'idy:', idy); + console.log('e:' + degree + ',' + r); + } + } + } + + // Store variable and make some calculations for the mouse event conversions + this.unroll.inner = inner; + this.unroll.outer = outer; + + // take the midpoint of the two disjoint ring center points in an attempt + // to normalize things + this.unroll.mid = new Point( + (outer['center'].x + inner['center'].x) / 2, + (outer['center'].y + inner['center'].y) / 2 + ); + this.unroll.width = this._rect.layers['unrolled-iris'].bounds.width; + + + if (this.unrollImagePath != '') { + const unrolledImage = new Image(); + unrolledImage.src = this.unrollImagePath; + unrolledImage.crossOrigin = 'anonymous'; + unrolledImage.onload = (evt) => { + this.replaceUnrollWithRaster(unrolledImage); + + // Check/Add Existing Annotation Text + this.updateUnrollAnnotations(); + }; + } else { + this.replaceUnrollWithRaster(undefined); + + // Check/Add Existing Annotation Text + this.updateUnrollAnnotations(); + } + + this.project.activate(); + } + + replaceUnrollWithRaster(imageElement: HTMLImageElement) { + const unrollLayer = this._rect.layers['unrolled-iris']; + unrollLayer.activate(); + + // Converts this layer, where each pixel is drawn as a 1x1 Rectangle, + // to an image + if (imageElement == undefined) { + const raster = unrollLayer.rasterize(null, false); + raster.name = 'unrolled-raster'; + + // Remove all the 1x1 Rectangles (to be replace by the raster image) + this._rect.activeLayer.removeChildren(); + + // Scale the project viewport to be the same size of the unrolled + // iris (currently 360 x rate [80]) + + // Add the "image-ized" version of the unrolled iris this layer, + // dramatically improving performance + this._rect.activeLayer.addChild(raster); + + this.unrolledRaster = raster; + + } else { + + // Remove all the 1x1 Rectangles (to be replace by the raster image) + this._rect.activeLayer.removeChildren(); + + const posX = imageElement.width / 2; + const posY = imageElement.height / 2; + const raster = new Raster(imageElement); + raster.name = 'unrolled-raster'; + + // Scale the project viewport to be the same size of the unrolled + // iris (currently 360 x rate [80]) + + // Add the "image-ized" version of the unrolled iris this layer, + // dramatically improving performance + this._rect.activeLayer.addChild(raster); + // Setting raster.position directly, as setting position with new Raster(imageElement, new Point(posX, posY)); created Raster with undefined position + raster.position = new Point(posX, posY); + this.unrolledRaster = raster; + this.unrollImagePath = ''; + } + + // store every "mousedown" + shiftKey events + // this event is ALWAYS fired before a "mousedrag" event + let lastDownEvt = null; + + // generic method to pass mouse events to the active IrisToolComponent + const evtHandler = (method) => (evt) => this._componentRef[method](evt); + + const dragUnrollOnShift = (evt) => { + if (evt.point) { + // compare event point to reference point + // (where the "mousedown" event was fired) + evt.point.x -= lastDownEvt.point.x; + evt.point.y -= lastDownEvt.point.y; // written this way because TypeScript + // doesn't like Point - Point + // (despite Paper.js allowing it) + + this.unrolledRaster.view.translate(evt.point); + + evt.preventDefault(); + evt.stopPropagation(); + } + }; + + const lastAltDrag = null; + + // specific handler for "mousedrag" to determine whether the event is + // 'special' or not (shiftKey || altKey) + const toolOrSpecialEvent = (evt) => { + if (evt.event.shiftKey && this.isInUnrollWindow) { + return dragUnrollOnShift(evt); + } + + if (!this._componentRef) { + lastDownEvt = evt; + + evt.preventDefault(); + evt.stopPropagation(); + return false; + } + + return evtHandler('onMouseDrag')(evt); + }; + + const onMouseDown = (evt) => { + if (evt.event.shiftKey || evt.event.altKey || !this._componentRef) { + lastDownEvt = evt; + + evt.preventDefault(); + evt.stopPropagation(); + return false; + } + return evtHandler('onMouseDown')(evt); + }; + + //////////////////////////////////////////////////////////////// + this.unrolledRaster.view.on({ + mousedown: (evt) => onMouseDown(evt), + mousedrag: (evt) => toolOrSpecialEvent(evt), + mouseleave: (evt) => { + // need to note whether or not the mouse cursor is in the window + // for zooming + this.isInUnrollWindow = false; + }, + + mouseenter: (evt) => { + this.isInWindow = false; + this.isInUnrollWindow = true; + } + }); + + //////////////////////////////////////////////////////////////// + + // adds a "ghost cursor" layer and indicator, to track mouse + // movements made within the iris + this.createGhostCursor(); + + this.unrollStatus = UnrollStatus.RECT_DRAWN; + + this.hasUnrolledSubject.next(true); + + //go back to origin canvas + this._project.activate(); + + // programmatically clicks the canvas element (exposed by paper.js) + // this is a shortcut to firing Angular's change detection + // and hiding the loading display + this._project.view.element.click(); + } + + /* + * Check if the given annotation is a drawn annotation. + * If a group is passed in, it's considered 'drawn' if and only if + * it contains solely drawn annotations. + * + * Checking `isDrawnAnnotation()` and removing if `true` implies that + * the entire group will be removed, not just leaf annotations. + * + */ + isDrawnAnnotation(annotation: Annotation) { + if (!annotation.hasChildren()) { + if (annotation.type == AnnotationType.POINT || + annotation.type == AnnotationType.CIRCLE || + annotation.type == AnnotationType.POLYGON || + annotation.type == AnnotationType.PENCIL || + annotation.type == AnnotationType.GROUP) { + return true; + } + return false; + } + + // First get the first child, see if it's a drawn annotation. + // Then, iterate through the rest of the children and `&&` + // the boolean to get the overall boolean value. + // + // First accessing the first child and calling `continue` on it + // in the loop is a stupid workaround for `LinkedMap`, since + // accessing by index is O(n). + const firstChild = annotation.getFirstChild(); + let drawn = this.isDrawnAnnotation(firstChild); + for (const child of annotation.children) { + if (child === firstChild) { + continue; + } + drawn = drawn && this.isDrawnAnnotation(child); + } + return drawn; + } + + getAnnotations(atype: AnnotationType) { + const annotations: Annotation[] = []; + Object.keys(this.itemsMap).forEach(key => { + const obj = this.itemsMap[key]; + if (obj['annotation'].type == atype) { + annotations.push(obj['annotation']); + } + }); + return annotations; + } + + removeAnnotations(atype: AnnotationType) { + const annotations: Annotation[] = this.getAnnotations(atype); + for (const a of annotations) { + this.removeItem(a.id, false); + this.removeAnnotation(a, true, true); + } + } + + checkingUserCreatedAnnotations(anno: Annotation, user: boolean, item?: InstanceType, beforeId?: string, parentId?: string, label?: InstanceType, fromRedo?: boolean) { + this.hasSaveButtonSelected = false; + var groupName = 'User Created Annotations'; + if (anno.type == AnnotationType.PDM) { + groupName = 'Individual Pupil Dilation Service'; + } else if (anno.type == AnnotationType.DUAL_PDM) { + groupName = 'Dual Pupil Dilation Service'; + } + if (!user || (this.selectedAnnotation !== undefined && this.selectedAnnotation.text == 'Iris Annotation Service')) { + this.selectedAnnotation = undefined; + } + // Check if it's a user created annotation + if (user && !parentId) { + this.addItem(anno, user, item, beforeId, this.userParentGroupId, label, fromRedo); + this.userAnnoGroup.push({id: anno.id, annotation: anno}); + if (fromRedo) { + this.userParentGroup = this.groupItems({no_parent: this.userAnnoGroup}, user, groupName, anno.parentId, fromRedo, true); + this.previousParentGroupId = undefined; + } else { + this.userParentGroup = this.groupItems({no_parent: this.userAnnoGroup}, user, groupName, null, fromRedo, true); + } + this.userParentGroupId = this.userParentGroup.id; + // Switching the current grouping annotation with the iris draw tool annotation from the past + const groupAnnotation = this.states.curr; + const firstAnno = this.states.past[this.states.past.length - 2]; + this.states.past[this.states.past.length - 2] = groupAnnotation; + // this set the last item in past to be the first annotation (the actual annotation that is initiates adding a group) + // Changing it to just pop that item off since we are not adding the current annotation until AFTER the user decides to make another annotation. + this.states.past.pop(); + this.states.curr = firstAnno; + } else { + this.addItem(anno, user, item, beforeId, parentId, label, fromRedo); + } + } + + addItem(annotation: Annotation, + user: boolean, + item?: InstanceType, + beforeId?: string, + parentId?: string, + label?: InstanceType, + fromRedo: boolean = false) { + this.userGroup.bringToFront(); + // Getting the parent Id to add the anno to if there is a selected Annotation + if (!annotation.isSystemGenerated && this.selectedAnnotation !== undefined) { + parentId = this.selectedAnnotation.id; + if (this.selectedAnnotation.type != AnnotationType.GROUP) { + parentId = this.selectedAnnotation.parentId; + } + } + + if (parentId) { + this.addAnnoToParent(annotation, item, beforeId, parentId); + } else { + if (beforeId) { + this.actualAnnotations.insertBefore(beforeId, annotation.id, annotation); + } else { + this.actualAnnotations.insert(annotation.id, annotation); + } + + if (item) { + this.userGroup.addChild(item); + } + } + + if (label) { + this.labelLayer.addChild(label); + } + + this.itemsMap[annotation.id] = { + annotation, + item, + label + }; + + this.propagateItemToUnroll(annotation); + + if (user) { + if (this.states.curr) { + this.states.past.push(this.states.curr); + } + + const data = { + parentId, + beforeId + }; + + this.states.curr = new State(annotation, StateType.ADD); + this.states.curr.data = data; + if (this.states.curr.annotation.type == AnnotationType.GROUP) { + this.states.curr.compRef = undefined; + } else { + this.states.curr.compRef = this.componentRef; + } + + if (!fromRedo) { + this.states.future.length = 0; + } + } + + } + + addAnnoToParent(annotation: Annotation, + item?: InstanceType, + beforeId?: string, + parentId?: string) { + const parent = this.itemsMap[parentId]; + + const parentItem = parent['item']; + const parentAnno = parent['annotation']; + + // If `beforeId` is specified, add the new item right after it. + // If not, just push to the end of the list + if (beforeId) { + parentAnno.addChildBefore(beforeId, annotation); + } else { + parentAnno.addChild(annotation); + } + + // Add `item` to paper.js Group only if `!!item` + if (item) { + parentItem.addChild(item); + } + } + + /* + * Update the unroll canvas with newly added/removed annotations. + * + * This should only be called if you want to update the *entire* + * unroll canvas (i.e., if the iris raster was modified) + */ + updateUnrollAnnotations() { + this.removeExistingAnnotationsFromUnroll(); + this.addExistingAnnotationsToUnroll(); + } + + /* + * Undo the most recently added annotation. + * If no annotation was added, nothing is done. + */ + undoAnnotation() { + // Don't do anything if no annotations were ever added + if (!this.states.curr) { + return; + } + + this.states.future.push(this.states.curr); + + const state = this.states.curr; + const anno = state.annotation; + const data = state.data; + this._componentRef = state.compRef; + this.selectedAnnotation = undefined; + + const user = !anno.isSystemGenerated; + + switch (this.states.curr.type) { + case StateType.ADD: + this.removeItem(anno.id, user, true); + // Check this method + if (!!this.componentRef) { + this.componentRef.resetData(); + } + break; + + case StateType.REMOVE: + const [item, label, usr] = this.drawAndLabelAnno(anno, true); + + this.addItem(anno, user, item, data.beforeId, data.parentId, label); + break; + + case StateType.BULK_REMOVE: + Object.keys(data['revertTo']).forEach(parentId => { + for (const obj of data['revertTo'][parentId]) { + const anno = obj['annotation']; + const [item, label, usr] = this.drawAndLabelAnno(anno); + parentId = parentId === 'no_parent' ? null : parentId; + this.addItem(anno, user, item, obj['beforeId'], parentId, label); + } + }); + break; + + case StateType.CLEAR: + case StateType.RESET: + this._clear(); + + for (const currAnno of data['revertTo']) { + const [item, label, usr] = this.drawAndLabelAnno(currAnno, true); + this.addItem(currAnno, user, item, null, null, label); + } + break; + + case StateType.GROUP_ONLY: + this.ungroupItems(state.data['revertTo'], + anno, + user); + break; + + case StateType.GROUP_ADD: + default: + break; + } + + this.states.curr = this.states.past.pop(); + + // This ensures that the save modal is prompted when navigating away + this.setIrisModified(true); + this.hasSaveButtonSelected = false; + } + + /* + * Redo the most recently undone annotation. + * If no annotation was undone, nothing is done. + */ + redoAnnotation() { + if (!this.states.future.length) { + return; + } + + // Set new current to last element in future + const currentAnno = this.states.future.pop(); + + this._componentRef = currentAnno.compRef; + + const state = currentAnno; + const anno = state.annotation; + let data; + if (!!state.data) { + data = state.data; + } else { + data = []; + } + + const user = !anno.isSystemGenerated; + + switch (state.type) { + case StateType.ADD: + // `state.data` looks like: { parentid, beforeId } + this.componentRef.reAddData(anno); + break; + + case StateType.REMOVE: + this.removeItem(anno.id, user); + break; + + case StateType.BULK_REMOVE: + this.deleteAllAnnotations(user); + break; + + case StateType.CLEAR: + this.clear(); + break; + case StateType.RESET: + this.reset(); + break; + + case StateType.GROUP_ONLY: + this.itemsMap[this.states.curr.annotation.id] = { + annotation: this.states.curr.annotation, + item: null, + label: null + }; + this.groupItems(data['revertTo'], user, anno.text, anno.id,true); + break; + + case StateType.GROUP_ADD: + default: + break; + } + + // This ensures that the save modal is prompted when navigating away + this.setIrisModified(true); + this.hasSaveButtonSelected = false; + } + + /* + * "Draw" the annotation provided by `anno`. + * Note that the drawn annotation (paper.js) is placed in `annotationLayer` + * If `insert` flag is `true`, the drawn annotation is + * inserted into the global `itemsMap`. + * + * Returns the drawn paper.js Item. + */ + drawItem(annotation: Annotation, insert?: boolean) { + this.annotationLayer.activate(); + + const self = this; + const _drawItem = function myself(anno: Annotation) { + let i; + + if (anno.type != AnnotationType.GROUP) { + i = self.coreFunctions.drawAnnotation(anno, self._iris); + } else { + i = new Group(); + for (const child of anno.children) { + const subitem = myself(child); + if (subitem) { + i.addChild(subitem); + } + } + } + + if (i) { + i.data.annotation = anno; + } + + if (insert) { + self.itemsMap[anno.id] = { + annotation: anno, + item: i + }; + + self.userGroup.addChild(i); + } + + // We can return empty paper.js Groups because + // there can be empty parent annotations + return i; + }; + + + const item = _drawItem(annotation); + if (item) { + item.data.annotation = annotation; + } + + + return item; + } + + /* + * Given an annotation, draw its label. + * Only applies for drawn annotations. + */ + drawLabel(annotation: Annotation, insert?: boolean) { + if (!this.drawAnnotationLabels) { + return null; + } + + this.labelLayer.activate(); + + const self = this; + const _drawAnnotationText = function myself(anno: Annotation) { + if (anno.type != AnnotationType.GROUP && anno.value) { + let tempPoint = new Point(0, 0); + let pointDefined = false; + + if (anno.value.points) { + + if (anno.value.points.length > 1) { + const points = new Array>(anno.value.points.length); + if (points.length) { + tempPoint = points[0][0]; + pointDefined = true; + } + } else if (anno.value.points.length == 1) { // Probably a Point or Circle + if (anno.type != AnnotationType.POINT && + anno.value.data['radius']) { + tempPoint = new Point(anno.value.points[0].x, anno.value.points[0].y - anno.value.data['radius']); + } else { + tempPoint = new Point(anno.value.points[0].x, anno.value.points[0].y); + } + pointDefined = true; + } + } + + let label; + if (pointDefined) { + const text = '(' + Math.floor(tempPoint.x) + ', ' + Math.floor(tempPoint.y) + ')'; + label = self.coreFunctions.drawAnnotationText(tempPoint, + text, + self.annotationColor, + self.annotationTextSize); + } + return label; + } else if (anno.type == AnnotationType.GROUP) { + let group; + if (anno.hasChildren()) { + group = new Group(); + for (const child of anno.children) { + const label = myself(child); + if (label) { + group.addChild(label); + + if (insert) { + self.itemsMap[child.id]['label'] = label; + } + } + } + } + return group; + } + return null; + }; + + return _drawAnnotationText(annotation); + } + + + /* + * Propagate the given annotation's label to the unroll canvas, + * if initialized. + */ + propagatePointTextItemToUnroll(annotation: Annotation) { + if (this.unrollStatus == UnrollStatus.UNINITIALIZED || + !this.drawAnnotationLabels) { + return; + } + + const layer = this._rect.layers['unrolled-items']; + + const self = this; + const _propagatePointTextItemToUnroll = function myself(anno: Annotation) { + if (anno.type == AnnotationType.POINT && anno.value) { + let loc = new Point(anno.value.points[0].x, anno.value.points[0].y); + loc = self.mainToUnroll(loc); + + if (loc.x != -1 && loc.y != -1) { + const pointText = layer.addChild(new PointText(loc)); + const text = (>pointText); + text.justification = 'center'; + text.fillColor = new Color(self.annotationColor); + text.content = '(' + loc.x + ', ' + loc.y + ')'; + text.bringToFront(); + } + } else if (anno.hasChildren()) { + for (const child of anno.children) { + myself(child); + } + } + }; + this._rect.activate(); + _propagatePointTextItemToUnroll(annotation); + this._project.activate(); + } + + redrawAnnotations(redrawPdmOnly: boolean = false) { + if (this._project) { + this.resetIris(); + this.clearLayers(); + + for (const anno of this.actualAnnotations) { + if (redrawPdmOnly && anno.text != "Individual Pupil Dilation Service") { + continue; + } + const [item, label, usr] = this.drawAndLabelAnno(anno, true); + } + + this.updateUnrollAnnotations(); + this.ghostCursorLayer.bringToFront(); + } + } + + /* + * Triggered when the mouse is over an annotation in annotation + * dialog. Highlight the annotation. + */ + annotationMouseOn(id: string) { + // Mouseleave has inconsistent firing. Verify that windows are "off" + this.isInUnrollWindow = false; + this.isInWindow = false; + + this.mouseOverLayer.bringToFront(); + this.mouseOverLayer.removeChildren(); + this.mouseOverLayer.activate(); + + const self = this; + const _annotationMouseOn = function myself(anno: Annotation) { + if (!anno.hasChildren()) { + if (!(anno.value && anno.value.data['strokeColor'])) { + return; + } + + switch (+anno.type) { + case AnnotationType.POINT: + case AnnotationType.CIRCLE: + const point = new Point(anno.value.points[0].x, anno.value.points[0].y); + self.mouseOverLayer.addChild( + self.coreFunctions.drawCircle(point, + anno.value.data['radius'], + self.highlightDrawColor, + anno.value.data['fillColor'], + Number(anno.value.data['strokeWidth']) + 2)); + break; + case AnnotationType.POLYGON: + self.mouseOverLayer.addChild( + self.coreFunctions.drawPolygon(anno.value.points, + self.highlightDrawColor, + Number(anno.value.data['strokeWidth']) + 2, + anno.value.data['fillColor'], + anno.value.data['fillAlpha'])); + break; + case AnnotationType.EYELID: + self.mouseOverLayer.addChild( + self.coreFunctions.drawPolygon(anno.value.points, + self.highlightDrawColor, + Number(anno.value.data['strokeWidth']) + 2, + anno.value.data['fillColor'], + anno.value.data['fillAlpha'], + false)); + break; + case AnnotationType.PENCIL: + self.mouseOverLayer.addChild( + self.coreFunctions.drawPath(anno.value.points, + self.highlightDrawColor, + Number(anno.value.data['strokeWidth']) + 2)); + break; + default: + break; + } + + return; + } + + for (const child of anno.children) { + myself(child); + } + }; + + const annotation = this.itemsMap[id]['annotation']; + _annotationMouseOn(annotation); + } + + /* + * Update the annotation label text size and redraw the + * annotations (which also redraws the labels). + */ + updateAnnotationTextSize(textSize: number) { + this.annotationTextSize = textSize; + this.redrawAnnotations(); + } + + /* + * Triggered when mouse leaves the annotation in annotation + * dialog. Un-highlight the annotation. + */ + annotationMouseOff(index) { + this.mouseOverLayer.removeChildren(); + } + + preparingRemoval(annotation: Annotation): [Annotation, string] { + var parentAnno: Annotation; + var beforeId: string; + const allAnno = this.getAnnotations(AnnotationType.GROUP); + allAnno.forEach(anno => { + var childrenAnnotations = anno.children['map']; + if (!!childrenAnnotations[annotation.id]) { + parentAnno = anno; + if (!!anno.children.getNext(annotation.id)) { + beforeId = anno.children.getNext(annotation.id).id; + } else { + beforeId = null; + } + } + }); + return [parentAnno, beforeId]; + } + + /* + * Remove an annotation specified by `id`, recursively. + */ + removeItem(id: string, user?: boolean, fromUndo: boolean = false, reapplyPdm = false) { + const obj = this.itemsMap[id]; + + if (!obj) { // Nothing to remove + return; + } + + const item = obj['item']; + const annotation = obj['annotation']; + const label = obj['label']; + + if (!user) { + user = !annotation.isSystemGenerated + } + + const removed = new Set(); + + const self = this; + const _removeItem = function myself(anno: Annotation, reapplyPdm) { + var needToResetPdmOriginal = false; + var needToResetDualPdmOriginal = false; + const l = self.itemsMap[anno.id]['label']; + const i = self.itemsMap[anno.id]['item']; + + // If they're drawn annotations, remove them from + // the canvas. But notice that we're not recursively + // removing the annotations from its parents, since + // we want to keep the reference in case of an undo. + if (l) { + l.remove(); + } + if (i) { + i.remove(); + } + + if (anno.type == AnnotationType.GROUP) { + for (const child of anno.children) { + var needToReset = myself(child, reapplyPdm); + needToResetPdmOriginal = needToReset.needToResetPdmOriginal; + needToResetDualPdmOriginal = needToReset.needToResetDualPdmOriginal; + } + } + + // We need to reset the _pdmOriginal/_dualPdmOriginal since there is no way to remove either pdms from the _iris + if (anno.type == AnnotationType.PDM && !reapplyPdm) { + needToResetPdmOriginal = true; + } + if (anno.type == AnnotationType.DUAL_PDM) { + needToResetDualPdmOriginal = true; + + } + + delete self.itemsMap[anno.id]; + removed.add(anno.id); + return {needToResetPdmOriginal, needToResetDualPdmOriginal}; + }; + + let parentAnno; + let annoAfter; + + // Save `annoAfter` to save it in `state.data['revertTo']` + // Also remove the current annotation from its parent. + if (annotation.parentId) { + parentAnno = this.itemsMap[annotation.parentId]['annotation']; + if (fromUndo && parentAnno.children.size == 1) { + annoAfter = parentAnno.getChildAfter(annotation.id); + parentAnno.removeChild(annotation.id); + this.removeItem(parentAnno.id, parentAnno.user, fromUndo, reapplyPdm); + // Get rid of the parent group since readding an annotation will automatically regenerate one. + this.states.past.pop(); + this.userAnnoGroup = []; + } else { + annoAfter = parentAnno.getChildAfter(annotation.id); + parentAnno.removeChild(annotation.id); + } + } else { + annoAfter = this.actualAnnotations.getNext(annotation.id); + + this.actualAnnotations.remove(annotation.id); + + // If we're deleting the userParentGroup then remove reference to it + if (annotation.id === this.userParentGroupId) { + this.previousParentGroupId = this.userParentGroupId; + this.userParentGroupId = undefined; + this.userAnnoGroup = []; + } + } + + // Remove items from the map and the parent list + if (this.itemsMap[annotation.id]) { + const needToReset = _removeItem(annotation, reapplyPdm); + if (needToReset.needToResetPdmOriginal) { + this._pdmOriginal = null; + } + if (needToReset.needToResetDualPdmOriginal) { + this._dualPdmOriginal = null; + } + } + if (!this.isDrawnAnnotation(annotation) && !reapplyPdm) { + // We need to reset the iris, since there's no way to + // undo non-drawn annotations (ex. crop) + this.resetIris(); + Object.keys(this.itemsMap).forEach(key => { + const anno = this.itemsMap[key]['annotation']; + + // Skip groups to avoid duplication. Skip removed + // annotations and drawn annotations since we only + // want to re-apply filtered annotations + if (anno.type == AnnotationType.GROUP || + removed.has(key) || + this.isDrawnAnnotation(anno)) { + return; + } + + this.coreFunctions.drawAnnotation(anno, self._iris); + }); + } + + this.updateUnrollAnnotations(); + + this.mouseOverLayer.removeChildren(); + this.tempToolLayer.removeChildren(); + + if (user) { + if (!fromUndo) { + if (this.states.curr) { + this.states.past.push(this.states.curr); + } + + const data = { + parentId: parentAnno ? parentAnno.id : null, + beforeId: annoAfter ? annoAfter.id : null + }; + this.states.curr = new State(annotation, StateType.REMOVE); + this.states.curr.data = data; + this.states.future.length = 0; + } + } + } + + showHideAnnotations(showAnnotations) { + this.userGroup.visible = showAnnotations; + this.userGroup.bringToFront(); + this.ghostCursorLayer.bringToFront(); + } + + /* + * Remove all drawn annotations such as Polygon, Point, Pencil, + * and Circle, recursively. + * + * If a group only contains drawn annotations, remove the entire group. + */ + deleteAllAnnotations(user?: boolean) { + const data = {}; + data['revertTo'] = {}; // In the format { parentId: [ {anno, index} ] } + + const self = this; + const _deleteDrawnAnnotations = function myself(anno: Annotation, + index: number) { + if (self.isDrawnAnnotation(anno)) { + const item = self.itemsMap[anno.id]['item']; + const label = self.itemsMap[anno.id]['label']; + + delete self.itemsMap[anno.id]; + item.remove(); + if (label) { + label.remove(); + } + + let parentId = anno.parentId; + let next; + + if (parentId) { + const parent = self.itemsMap[parentId]['annotation']; + next = parent.getChildAfter(anno.id); + parent.removeChild(anno.id); + } else { + parentId = 'no_parent'; + + next = self.actualAnnotations.getNext(anno.id); + self.actualAnnotations.remove(anno.id); + } + + if (!data['revertTo'][parentId]) { + data['revertTo'][parentId] = []; + } + + data['revertTo'][parentId].push({ + annotation: anno, + beforeId: next ? next.id : null + }); + } else if (anno.hasChildren()) { + let i = 0; + for (const child of anno.children) { + myself(child, i); + i++; + } + } + }; + + let i = 0; + for (const anno of this.actualAnnotations) { + _deleteDrawnAnnotations(anno, i); + i++; + } + + if (user) { + if (this.states.curr) { + this.states.past.push(this.states.curr); + } + + const newAnno = new Annotation('Bulk Removal', AnnotationType.UNKNOWN, 'window-close-o'); + this.states.curr = new State(newAnno, StateType.BULK_REMOVE); + this.states.curr.data = data; + this.states.future.length = 0; + } + } + + /* + * + * Takes two annotations, recursively finds common parent. + * Returns the annotation whose common parent's children has + * the least index. + * + * Example) Suppose we have the following structure: + * + * Group_A + * - Group_I + * + Anno_1 + * - Group_II + * + Anno_2 + * + * When we call func(Anno_1, Anno_2), the function will iterate + * til `Group_A` (the common grandparent) and return `Anno_1` + * because `Anno_1` ultimately comes before `Anno_2` since `Group_I` + * comes before `Group_II` under the common (grand)parent `Group_A` + * + */ + private getAnnotationWithLeastIndex(anno1: Annotation, anno2: Annotation) { + if (!anno1 || !anno2) { + return anno1 || anno2; + } + if (anno1.parentId == anno2.parentId) { // Found common parent + if (!anno1.parentId) { + return this.actualAnnotations.compareIndices(anno1.id, anno2.id) < 0 ? + anno1 : anno2; + } else { + const commonParent = this.itemsMap[anno1.parentId]['annotation']; + return commonParent.children.compareChildIndices(anno1.id, anno2.id) < 0 ? + anno1 : anno2; + } + } + + const obj1 = this.itemsMap[anno1.parentId]; + const obj2 = this.itemsMap[anno2.parentId]; + return this.getAnnotationWithLeastIndex(obj1 ? obj1['annotation'] : null, + obj2 ? obj2['annotation'] : null); + } + + /* + * Ungroup the group `anno`. + * `obj` should be in the form of { parentId: [{ id, index }] } + * that was stored in state.data['revertTo'] where state.type === StateType.GROUP. + */ + ungroupItems(obj: any, + anno: Annotation, + user: boolean) { + // Nothing to ungroup + if (!obj || + (Object.keys(obj).length === 0 && obj.constructor === Object)) { + return; + } + + // First, remove the group + const item = this.itemsMap[anno.id]['item']; + const label = this.itemsMap[anno.id]['label']; + + if (anno.parentId) { + const parent = this.itemsMap[anno.parentId]['annotation']; + parent.removeChild(anno.id); + } else { + this.actualAnnotations.remove(anno.id); + } + + delete this.itemsMap[anno.id]; + + if (item) { + item.remove(); + } + if (label) { + label.remove(); + } + + + // Add items back into the project + Object.keys(obj).forEach(parentId => { + const arr = obj[parentId]; + + for (const info of arr) { + const index = info['index']; + const child = this.itemsMap[info['id']]; + + if (!child) { + continue; + } + const childItem = child['item']; + const childAnno = child['annotation']; + + if (parentId === 'no_parent') { + this.actualAnnotations.insert(childAnno.id, childAnno, index); + this.userGroup.insertChild(index, childItem); + childAnno.parentId = null; + } else { + const parent = this.itemsMap[parentId]; + parent['annotation'].addChild(childAnno, index); + parent['item'].insertChild(index, childItem); + } + } + }); + } + + /* + * Group annotations stored in `obj`. + * `obj` should be in the form of { parentId: [{ id, index }] }. + * If `existingId` is specified, when a new group Annotation is created, + * set its id as `existingId`. + */ + groupItems(obj: any, + user: boolean, + groupName?: string, + existingId?: string, + fromRedo?: boolean, + fromChecking: boolean = false): Annotation { + // No group to make + if (!obj || (Object.keys(obj).length === 0 && obj.constructor === Object)) { + return; + } + + let deepestParent; + let deepestParentDepth = 0; + + const items = []; + const labels = []; + const annotations = []; + + let sibling; + + // First, iterate thru all the selected annotations and + // find the deepest parent to add the new group to. + // + // Note that obj is in the form of + // { parentId: [ { id, index }, ... ], ... } + Object.keys(obj).forEach(parentId => { + let parent; + + if (parentId !== 'no_parent') { + parent = this.itemsMap[parentId]['annotation']; + } + + const depth = this.getAnnotationDepth(parent); + + if (depth > deepestParentDepth) { + deepestParent = parent; + deepestParentDepth = depth; + } else if (depth == deepestParentDepth) { + deepestParent = this.getAnnotationWithLeastIndex(deepestParent, parent); + } + }); + + // Set the new group's sibling that will come after in order + // TODO: Look into -> throw error several times + if (!deepestParent) { + sibling = this.itemsMap[obj['no_parent'][0].id]['annotation']; + } else { + sibling = this.itemsMap[obj[deepestParent.id][0].id]['annotation']; + } + + // Now, remove all items except the deepst parent's + // least-index child + Object.keys(obj).forEach(parentId => { + const children = obj[parentId]; + let parent; + + if (parentId !== 'no_parent') { + parent = this.itemsMap[parentId]['annotation']; + } + + for (const child of children) { + annotations.push(this.itemsMap[child.id]['annotation']); + items.push(this.itemsMap[child.id]['item']); + labels.push(this.itemsMap[child.id]['label']); + + // Skip removing `sibling` because it'll be used + // as a reference when inserting the new group + if (child.id === sibling.id) { + continue; + } + + if (parent) { + parent.removeChild(child.id); + } else { + this.actualAnnotations.remove(child.id); + } + } + }); + + // Create the new group and a new annotation + const group = new Group(); + let newAnno; + if (groupName) { + newAnno = new Annotation(groupName, AnnotationType.GROUP, 'fa-folder-o'); + } else { + newAnno = new Annotation('Group', AnnotationType.GROUP, 'fa-folder-o'); + } + newAnno.isSystemGenerated = !user; + group.data.annotation = newAnno; + + const labelGroup = new Group(labels); + + if (existingId) { + newAnno.id = existingId; + } else if (this.previousParentGroupId) { + newAnno.id = this.previousParentGroupId; + } + + const beforeId = sibling.id; + const deepestParentId = deepestParent ? deepestParent.id : null; + + this.addItem(newAnno, user, group, beforeId, deepestParentId, labelGroup, fromRedo); + + // Now remove sibling that was used as an anchor + if (deepestParent) { + deepestParent.removeChild(sibling.id); + } else { + this.actualAnnotations.remove(sibling.id); + } + + // Setting the parent id to any of the annotations that don't have a parent id + for (const anno of annotations) { + if (!anno.parentId) { + anno.parentId = newAnno.id; + } + } + + newAnno.appendChildren(annotations); + group.addChildren(items); + + if (user) { + if (fromChecking) { + if (this.states.curr) { + this.states.past.push(this.states.curr); + } + } + const data = { + revertTo: obj + }; + this.states.curr = new State(newAnno, StateType.GROUP_ONLY); + this.states.curr.data = data; + this.states.curr.compRef = undefined; + if (!fromRedo) { + this.states.future.length = 0; + } + } + + if (!user === undefined) { + annotations.forEach(annotation => { + if (!annotation.isSystemGenerated) { + newAnno.isSystemGenerated = false; + return newAnno; + } else { + newAnno.isSystemGenerated = true; + } + }); + } + + return newAnno; + } + + public temp_data: ImageData; + initTool(name: string, layerKey?: string) { + + this.temp_data = this._iris.getImageData(null); + this.irisModified = this.irisModified || this.tempToolIrisModification; + this._project.activate(); + this.annotationLayer.activate(); + return this.annotationLayer; + } + + zoom(evt) { + if (this.isInWindow) { + const delta = Math.max(-1, Math.min(1, (evt.wheelDelta || -evt.detail))); + const factor = 1.05; // zoom factor + if (delta < 0) { + this._project.view.zoom /= factor; + this.ghostCursor.bounds.width *= factor; + this.ghostCursor.bounds.height *= factor; + } else if (delta > 0) { + this._project.view.zoom *= factor; + this.ghostCursor.bounds.width /= factor; + this.ghostCursor.bounds.height /= factor; + } + } else if (this.isInUnrollWindow) { + const delta = Math.max(-1, Math.min(1, (evt.wheelDelta || -evt.detail))); + const factor = 1.05; // zoom factor + if (delta < 0) { + this.unrolledRaster.view.zoom /= factor; + } else if (delta > 0) { + this.unrolledRaster.view.zoom *= factor; + } + } + } + + bindMouseEvents(layer: any) { + const self = this; + + /** + * Dev Note: + * Purposely did not use Paper.js MouseEvent.delta for implementing Move/Zoom functionality + * It caused odd behavior when the mouse was moved at slower speeds + */ + + // generic method to pass mouse events to the active IrisToolComponent + const evtHandler = (method) => (evt) => { + self.convertMainEventToUnroll(evt); + + if (self._componentRef) { + if (self.disableTools) { + return; + } + // evt.target is always the view, because the event + // listeners are mounted on the view object + + return self._componentRef[method](evt); + } + }; + + // store every "mousedown" + shiftKey events + // this event is ALWAYS fired before a "mousedrag" event + let lastDownEvt = null; + + // specific handler for "mousedrag" + shiftKey + // (assumes "mousedown" fired previously) + const dragIrisOnShift = (evt) => { + if (evt.point) { + // compare event point to reference point + // (where the "mousedown" event was fired) + evt.point.x -= lastDownEvt.point.x; + evt.point.y -= lastDownEvt.point.y; // written this way because + // TypeScript doesn't like Point - + // Point (despite Paper.js allowing it) + + this._project.view.translate(evt.point); + + evt.preventDefault(); + evt.stopPropagation(); + } + }; + + const lastAltDrag = null; + + // specific handler for "mousedrag" to determine + // whether the event is 'special' or not (shiftKey || altKey) + const toolOrSpecialEvent = (evt) => { + if (evt.event.shiftKey && this.isInWindow) { + return dragIrisOnShift(evt); + } + + return evtHandler('onMouseDrag')(evt); + }; + + // specific handler for "mousedown", stores last "mousedown" + shiftKey + // event + const onMouseDown = (evt) => { + if (evt.event.shiftKey || evt.event.altKey) { + lastDownEvt = evt; + + evt.preventDefault(); + evt.stopPropagation(); + return false; + } + + return evtHandler('onMouseDown')(evt); + }; + + // apply these mouse event handlers to the active layer + layer.on({ + mouseup: (evt) => evtHandler('onMouseUp')(evt), + mousedown: (evt) => onMouseDown(evt), + mousedrag: (evt) => toolOrSpecialEvent(evt), + mousemove: (evt) => evtHandler('onMouseMove')(evt), + mouseleave: (evt) => { + //need to note whether or not the mouse cursor is in the window + //since mouse drag gets called when you drag the mouse outside + //of the window and additionally for zooming + this.isInWindow = false; + }, + + // Used to set canvas context (when tools are not switched) + mouseenter: (evt) => { + this.isInUnrollWindow = false; + this.isInWindow = true; + this._project.activate(); + } + }); + } + + removeExistingAnnotationsFromUnroll() { + if (this.unrollStatus == UnrollStatus.UNINITIALIZED) { + return; + } + if (!!this._rect) { + const layer = this._rect.layers['unrolled-items']; + layer.removeChildren(); + } + } + + addExistingAnnotationsToUnroll() { + for (const anno of this.actualAnnotations) { + this.propagateItemToUnroll(anno); + this.propagatePointTextItemToUnroll(anno); + } + } + + /* + * Propagate an annotation to the unroll canvas, if initialized. + * Basically only propagates Point annotations. + * Should be called if adding a single annotation to the unroll + * canvas. + */ + propagateItemToUnroll(annotation: Annotation) { + if (this.unrollStatus == UnrollStatus.UNINITIALIZED || !annotation) { + return; + } + + const self = this; + const _propagateAnnotationToUnroll = function myself(anno: Annotation) { + // Only propagate Points + if (anno.type == AnnotationType.POINT) { + // Copy cosmetic settings from the original Item + const config = { + strokeWidth: anno.value.data['strokeWidth'], + strokeColor: anno.value.data['strokeColor'], + fillColor: anno.value.data['fillColor'], + pointRadius: anno.value.data['radius'] || null + }; + + // x values of all converted points are a function of the degree of the angle + // therefore crossing the "boundary" results in (n).x - (n-1).x ~= rect.width + // Compare each point - (point-1) against a threshold of 85% the width of the rect + // to determine when a "boundary jump" takes place within a single Item or Path + //const boundaryJumpThersold = this._rect.view.bounds.width * 0.85; + const centerPoint = new Point(anno.value.points[0].x, anno.value.points[0].y); + + const tempCenter = self.mainToUnroll(centerPoint); + anno.value.data['unrollX'] = tempCenter.x; + anno.value.data['unrollY'] = tempCenter.y; + + let circle; + if (tempCenter.x != -1 && tempCenter.y != -1) { + circle = new Path.Circle(tempCenter, config.pointRadius); + circle.fillColor = config.fillColor; + circle.strokeWidth = config.strokeWidth; + circle.strokeColor = config.strokeColor; + } + return circle; + } else if (anno.type == AnnotationType.GROUP) { + const group = new Group(); + for (const child of anno.children) { + const drawn = myself(child); + if (drawn) { + group.addChild(drawn); + } + } + if (group.hasChildren()) { + return group; + } + } + return null; + }; + + if (!!this._rect) { + this._rect.activate(); + const layer = this._rect.layers['unrolled-items']; + + // convert the single iris Item into a Group of Items to be drawn on the unrolled canvas + // (this prevents erroneous lines connecting two on separate sides of the boundary) + const itemGroup = _propagateAnnotationToUnroll(annotation); + if (itemGroup) { + layer.addChild(itemGroup); + } + } + // Reactivate main canvas + this._project.activate(); + } + + /* + * Converts a main canvas mouse event to an unroll event. + */ + convertMainEventToUnroll(evt) { + if (this.unrollStatus === UnrollStatus.RECT_DRAWN) { + if (this.unroll.outer.contains(evt.point) && !this.unroll.inner.contains(evt.point)) { + this._unrollGhostCursor.bounds.center = this.mainToUnroll(evt.point); + } + } + } + + /* + * Given a Point in the main canvas, create a Point to place + * in the unroll canvas. + */ + mainToUnroll(point: InstanceType): InstanceType { + // returns: [-1, -1] for points not in the unroll + const ret = point.clone(); + ret.x = -1; + ret.y = -1; + + if (!this.unroll.outer.contains(point) || this.unroll.inner.contains(point)) { + return ret; + } + + ret.x = Math.round(point.x); + ret.y = Math.round(point.y); + + let modPoint = this.lookupTable[ret.x][ret.y]; + if (this.lookupTable[ret.x][ret.y][0] == -1 + && this.lookupTable[ret.x][ret.y][1] == -1) { + + for (let x = 1; x < Math.min(this._iris.width, this._iris.height) - 1; x++) { + modPoint = this.lookupTable[ret.x + x][ret.y]; + if (modPoint[0] != -1 && modPoint[1] != -1) { + break; + } + + modPoint = this.lookupTable[ret.x + x][ret.y + x]; + if (modPoint[0] != -1 && modPoint[1] != -1) { + break; + } + + modPoint = this.lookupTable[ret.x + x][ret.y - x]; + if (modPoint[0] != -1 && modPoint[1] != -1) { + break; + } + + modPoint = this.lookupTable[ret.x][ret.y - x]; + if (modPoint[0] != -1 && modPoint[1] != -1) { + break; + } + + modPoint = this.lookupTable[ret.x][ret.y + x]; + if (modPoint[0] != -1 && modPoint[1] != -1) { + break; + } + + modPoint = this.lookupTable[ret.x - x][ret.y - x]; + if (modPoint[0] != -1 && modPoint[1] != -1) { + break; + } + + modPoint = this.lookupTable[ret.x - x][ret.y]; + if (modPoint[0] != -1 && modPoint[1] != -1) { + break; + } + + modPoint = this.lookupTable[ret.x - x][ret.y + x]; + if (modPoint[0] != -1 && modPoint[1] != -1) { + break; + } + + } + } + + ret.x = modPoint[0]; + ret.y = modPoint[1]; + return ret; + } + + /* + * Reset the annotations to revert back to the last saved point. + */ + reset(user?: boolean) { + if (user) { + const data = { + revertTo: this.actualAnnotations.toList() + }; + if (this.states.curr) { + this.states.past.push(this.states.curr); + } + + const anno = new Annotation('Reset', AnnotationType.UNKNOWN, null); + + this.states.curr = new State(anno, StateType.RESET); + this.states.curr.data = data; + this.states.future.length = 0; + } + + this._clear(); + var pdmAnno; + if (this.originalAnnotations && this.originalAnnotations.length) { + for (let i = 0; i < this.originalAnnotations.length; i++) { + const anno = this.originalAnnotations[i]; + // Both PDM annotations need to be added a different way. + if (anno.text == "Individual Pupil Dilation Service" || anno.text == "Dual Pupil Dilation Service") { + pdmAnno = anno; + } + const [item, label, usr] = this.drawAndLabelAnno(anno); + this.addItem(anno, usr, item, null, null, label); + + // Replace with deep clone + this.originalAnnotations[i] = Annotation.clone(anno); + } + return pdmAnno; + } + + + this.updateUnrollAnnotations(); + if (this.originalAnnotations && this.originalAnnotations.length) { + for (const item of this.originalAnnotations) { + if ((item.parentId == null || item.parentId == "") && item.children != null && item.text !== 'Iris Annotation Service' && item.text !== 'Individual Pupil Dilation Service' && item.text != "Dual Pupil Dilation Service") { + this.userParentGroupId = item.id; + break; + } else { + this.userParentGroupId = undefined; + } + } + } else { + this.userParentGroupId = undefined; + } + this.userGroup = new Group({name: 'user'}); + this.userAnnoGroup = []; + this.userParentGroup = []; + this.selectedAnnotation = undefined; + } + + /* + * Clear the iris and the annotation list. + */ + private _clear() { + this.actualAnnotations.clearAll(); + this.itemsMap = {}; + this.hidden.clear(); + + this.resetIris(); + this.clearLayers(); + + this.annotationLayer.activate(); + + this.states = { + past: [], // to undo + curr: null, // last added + future: [], // to redo + }; + this.previousParentGroupId = undefined; + + } + + /* + * Remove all annotations and clear the canvas. + */ + clear(user?: boolean) { + if (user) { + const data = { + revertTo: this.actualAnnotations.toList(), + }; + + if (this.states.curr) { + this.states.past.push(this.states.curr); + } + + const anno = new Annotation('Clear', AnnotationType.UNKNOWN, null); + + this.states.curr = new State(anno, StateType.CLEAR); + this.states.curr.data = data; + this.states.future.length = 0; + } + + this._clear(); + this.createClone(); + this.updateUnrollAnnotations(); + this.previousParentGroupId = this.userParentGroupId; + this.userParentGroupId = undefined; + this.selectedAnnotation = undefined; + this.userAnnoGroup = []; + + } + + /* + * Clear all the paper.js layers. + */ + clearLayers() { + this.project.activate(); + + this.mouseOverLayer.removeChildren(); + this.tempToolLayer.removeChildren(); + this.labelLayer.removeChildren(); + + this.annotationLayer.removeChildren(); + this.userGroup.removeChildren(); + this.generatedGroup.children['highlight'].removeChildren(); + this.generatedGroup.children['item'].removeChildren(); + this.annotationLayer.addChild(this.userGroup); + this.annotationLayer.addChild(this.generatedGroup); + } + + /* + * Reset the raster and clear the layers. + */ + resetIris() { + // reset to original rotation + this.coreFunctions.rotate(this.iris, -this._project.view.rotation); + + this.irisModified = false; + this.tempToolIrisModification = false; + + this.irisImageLayer.removeChildren(); + // We need to check if there is a PDM applied when we are deleting a single annotation. + if (this._pdmOriginal) { + this._iris = this.createIrisRaster(this._pdmOriginal); + } else if (this._dualPdmOriginal){ + this._iris = this.createIrisRaster(this._dualPdmOriginal); + } else { + this._iris = this.createIrisRaster(this._original); + } + + this.irisImageLayer.addChild(this._iris); + } + + addTshepiiPoly(anno: Annotation, annotationColor: string): InstanceType { + const points = anno.value.points; + + const path = new Path(); + path.strokeColor = new Color(annotationColor); + path.strokeColor.alpha = this.currUser.userSetting.tshepiiStrokeOpacity / 100; + path.strokeWidth = 1; + + path.fillColor = new Color(annotationColor); + path.fillColor.alpha = .1; + + for (const point of points) { + path.add(point); + }//end of for loop + + path.closed = true; + + this.generatedGroup.children['item'].addChild(path); + + return path; + } + + addTshepiiHighlight(anno: Annotation, annotationColor: string) { + const points = anno.value.points; + + const path = new Path(); + path.strokeColor = new Color(annotationColor); + path.strokeColor.alpha = 0.0; + path.strokeWidth = 2; + path.fillColor = new Color(annotationColor); + path.fillColor.alpha = .2; + + for (const point of points) { + path.add(point); + }//end of for loop + + path.closed = true; + + this.generatedGroup.children['highlight'].addChild(path); + } + + removeTshepiiItems() { + this.generatedGroup.children['item'].removeChildren(); + } + + removeTshepiiHighlights() { + this.generatedGroup.children['highlight'].removeChildren(); + } + + showHideAnnotation(initialId: string) { + const hide = !this.hidden.has(initialId); + const initialObj = this.itemsMap[initialId]; + const initalAnno = this.itemsMap[initialId]['annotation']; + // If the whole group is being hidden, it marks which annotations were previously hidden. + if (initalAnno.type == AnnotationType.GROUP && hide) { + this.previouslyFiltered = new Set(this.hidden); + } + + const self = this; + const _hideAnnotation = function myself(anno: Annotation) { + if (hide) { // Hide + this.hidden.add(anno.id); + } + if (anno.type != AnnotationType.GROUP) { + if (initialObj['item']) { + initialObj['item'].visible = !hide; + if (initialObj['label']) { + initialObj['label'].visible = !hide; + } + } + + } else { // This is a group + for (const child of anno.children) { + _hideAnnotation(child); + } + } + }.bind(this); + + // Check to see if the itemId is in the hidden set. + // Deletes the id from the set and draws the annotation if: + // 1) id is NOT in hidden set and it's a hide, or 2) if id IS in hidden and it's a show + const _checkHidden = function _checkHidden(anno: Annotation) { + if ((this.hidden.has(anno.id) && !hide && anno.id == initialId) || + (!this.hidden.has(anno.id) && hide)) { + // Apply non-hidden annotations to image + this.hidden.delete(anno.id); + this.coreFunctions.drawAnnotation(anno, this._iris); + } + }.bind(this); + + _hideAnnotation(initalAnno); + + if (this.hidden.size) { + // If we're hiding annotations, + // reset the entire iris + if (hide) { + this.resetIris(); + } + + Object.keys(this.itemsMap).forEach(itemId => { + const itemAnno = this.itemsMap[itemId]['annotation']; + + // Skips itemAnno if it's of Type GROUP + if (itemAnno.type === AnnotationType.GROUP) { + return; + } + + // If it's hide, it checks to see if the itemAnno is in the hidden set + if (hide) { + _checkHidden(itemAnno); + } else { // Show + if (initalAnno.type !== AnnotationType.GROUP) { + _checkHidden(itemAnno); + } else { // This is for when initial annotation is of Type GROUP + // Checks to see if the annotation was previously hidden before the whole group was hidden + // Deletes the id from hidden set and draws the annotation if: + // 1) Id IS in previouslyFiltered set and it's a hide, or 2) if the id is NOT in previouslyFiltered set and it's a show + if ((this.previouslyFiltered.has(itemId) && hide) || + (!this.previouslyFiltered.has(itemId) && !hide)) { + // Apply hidden filters to the iris + this.hidden.delete(itemId); + this.coreFunctions.drawAnnotation(itemAnno, self._iris); + } + // Removes the group id from the hidden set + this.hidden.delete(initialId); + } + } + }); + } + } + + /* + * Check if an annotation specified by `id` exists. + */ + annotationExists(id: string) { + return !!this.itemsMap[id]; + } + + public revertToState(index: number) { + if (!this.states.past.length) { + return; + } + + while (this.states.past.length > index) { + this.undoAnnotation(); + } + } + + private getAnnotationDepth(annotation: Annotation) { + const self = this; + const _getAnnotationDepth = function myself(anno: Annotation) { + if (!anno || !anno.parentId) { + return 0; + } + return 1 + myself(self.itemsMap[anno.parentId]); + }; + + return _getAnnotationDepth(annotation); + } + + public removeAnnotation(anno: Annotation, + recursive?: boolean, + removeFromParent?: boolean) { + delete this.itemsMap[anno.id]; + + if (removeFromParent) { + if (anno.parentId) { + const parent = this.itemsMap[anno.parentId]; + parent['annotation'].removeChild(anno.id); + } else { + this.actualAnnotations.remove(anno.id); + } + } + + if (recursive && anno.hasChildren()) { + for (const child of anno.children) { + this.removeAnnotation(child, true, removeFromParent); + } + } + } + + private createGhostCursor() { + if (this._unrollGhostCursor == undefined) { + const cursorLayer = this._rect.addLayer(new Layer({name: 'ghost-cursor'})); + this._unrollGhostCursor = >cursorLayer.addChild(new Path.Circle({ + center: new Point(0, 0), + radius: 5, + strokeColor: '#000', + fillColor: '#ff0', + })); + cursorLayer.bringToFront(); + } + } + + addIrisAnnotationResponse(irisResponse: IrisAnnotationResponse) { + this.pullAnnoButtonClicked = true; + const group = []; + let index = 0; + for (const poly of irisResponse.polygonList) { + if (poly.outline.length > 0) { + const annotation = new Annotation(poly.name, AnnotationType.POLYGON, 'fa-cube'); + annotation.isSystemGenerated = true; + const points = []; + for (const seg of poly.outline) { + points.push({x: seg.x, y: seg.y}); + } + annotation.value = { + points, + data: { + strokeColor: poly.color, + fillColor: poly.color, + strokeWidth: this.strokeWidth, + fillAlpha: 0.2 + } + }; + + const polygon = this.coreFunctions.drawPolygon(annotation.value.points, annotation.value.data['strokeColor'], annotation.value.data['strokeWidth'], annotation.value.data['fillColor'], annotation.value.data['fillAlpha']); + polygon.data.dannotation = annotation; + const text = '(' + Math.floor(points[0]['x']) + ', ' + Math.floor(points[0]['y']) + ')'; + let label; + + if (this.drawAnnotationLabels) { + label = this.coreFunctions.drawAnnotationText( + new Point(points[0]), + text, + this.annotationColor, + this.annotationTextSize + ); + } + + group.push({id: annotation.id, index: index++}); + this.addItem(annotation, !annotation.isSystemGenerated, polygon, null, null, label); + } else { + console.log('Poly: ' + poly.name + ' is empty'); + } + } + + this.groupItems({no_parent:group}, false, 'Iris Annotation Service'); + this.redrawAnnotations(); + } + + // Unused but keeping in case we need to figure out if the annotation is in past states + currAnnoInPast() { + return this.states.past.some(pastState => { + return pastState.annotation.id == this.states.curr.annotation.id; + }) + } + + selectedAnnotationChanged($event: Annotation) { + this.selectedAnnotation = $event; + } + + drawAndLabelAnno(anno: Annotation, insert?: boolean) { + const item = this.drawItem(anno, insert); + const label = this.drawLabel(anno, insert); + const usr = !anno.isSystemGenerated; + + return [item, label, usr]; + } +} diff --git a/web/client/src/app/iris-display/tools/iris-tool.component.ts b/web/client/src/app/iris-display/tools/iris-tool.component.ts new file mode 100644 index 0000000..af99206 --- /dev/null +++ b/web/client/src/app/iris-display/tools/iris-tool.component.ts @@ -0,0 +1,90 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { + Component, Directive, Input, ViewChild, ViewContainerRef, + ComponentFactoryResolver, OnChanges, AfterContentInit + } from '@angular/core'; +import { ToolManagerService } from '../tool-manager.service'; +import { IrisToolRegistry } from './tool.registry'; + +@Directive({ + selector: '[appToolHost]' +}) +export class ToolHostDirective { + constructor(public viewContainerRef: ViewContainerRef) { + } +} + +//this is the middle bar underneath the tool bar +@Component({ + selector: 'app-iris-tool', + template: ` +
+
+ +
+
+ `, +}) +export class IrisToolComponent implements AfterContentInit, OnChanges { + _tool: string; + @Input() index: number; + @Input() instanceId: string; + @ViewChild(ToolHostDirective, {static: true}) toolHost: ToolHostDirective; + + constructor(private toolRegistry: IrisToolRegistry, + public toolManager: ToolManagerService, + private componentFactoryResolver: ComponentFactoryResolver) { + + } + + @Input() set tool(val: string) { + this._tool = val; + } + + get tool(): string { + return this._tool; + } + + ngAfterContentInit() { + this.loadToolComponent(); + } + + // Changes the loaded Component each time a different tool is activated + // This logic only works AFTER ViewInit has fired (due to the dependency on @ViewChild()) + ngOnChanges(changes) { + if (!!changes['tool'] && !changes['tool'].firstChange) { + this.loadToolComponent(); + } + } + + lookUpToolComponent() { + return this.toolRegistry.getComponent(this.tool).component; + } + + loadToolComponent() { + // Get Derived Tool Component Class from Tool Registry + const ToolComponent = this.lookUpToolComponent(); + + // Resolve the Component via Angular black magic + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(ToolComponent); + + // Clear the current View Container ("host") + const viewContainerRef = this.toolHost.viewContainerRef; + viewContainerRef.clear(); + + // Create/place the component inside the View Container + const componentRef = viewContainerRef.createComponent(componentFactory); + + // Using the componentRef variable, append any additional data to the new component as needed + this.toolManager.setCompRef(componentRef); + } +} diff --git a/web/client/src/app/iris-display/tools/tool-components/adaptiveContrast.tool.ts b/web/client/src/app/iris-display/tools/tool-components/adaptiveContrast.tool.ts new file mode 100644 index 0000000..7413dfa --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/adaptiveContrast.tool.ts @@ -0,0 +1,108 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { Component } from '@angular/core'; +import { ToolManagerService } from '../../tool-manager.service'; +import { Rectangle } from 'paper'; +import { CoreFunctions } from '../../core/core-functions'; +import { Annotation, AnnotationType } from '../../../types/annotation'; + +@Component({ + template: ` + + Clip: + + + + + ` +}) +export class AdaptiveContrastTool extends IrisDrawingTool implements RequiredMembers { + name = 'AdaptiveContrast'; + static icon = 'fas fa-th'; + static id = 'AdaptiveContrast'; + static type: IrisToolType = IrisToolType.IMAGE; + protected _layerKey = 'iris'; + + userParentGroupId: string; + + coreFunctions: CoreFunctions = new CoreFunctions(); + + annotation: Annotation; + + clipFactor = 0.0; + tileDivisor = 8; + fromRedo = false; + + constructor( + protected toolManager: ToolManagerService + ) { + super(toolManager); + } + + onToolInit() { + this.toolManager.createClone(); + this.annotation = new Annotation(AdaptiveContrastTool.id, AnnotationType.CLAHE, AdaptiveContrastTool.icon); + this.annotation.value = { + points: undefined, + data: { + tileDivisor: this.tileDivisor, + clipFactor: this.clipFactor + } + }; + } + + onApply() { + if (this.fromRedo) { + this.toolManager.createClone(); + } + this.userParentGroupId = this.toolManager.userParentGroupId; + this.toolManager._iris.setImageData(this.toolManager._irisClone.getImageData(new Rectangle(0, 0, this.toolManager._iris.width, this.toolManager._iris.height))); + this.clahe(); + + this.toolManager.setIrisModified(true); + } + + clahe() { + this.annotation.value = { + points: undefined, + data: { + tileDivisor: this.tileDivisor, + clipFactor: this.clipFactor + } + }; + + let imageData: ImageData = this.toolManager._irisClone.getImageData(new Rectangle(0, 0, this.toolManager._iris.width, this.toolManager._iris.height)); + imageData = this.coreFunctions.clahe(imageData, this.tileDivisor, this.clipFactor); + this.toolManager._iris.setImageData(imageData); + + if (!this.toolManager.annotationExists(this.annotation.id)) { + this.toolManager.checkingUserCreatedAnnotations(this.annotation, true, null, null, this.userParentGroupId, null, this.fromRedo); + } + } + + resetData() { + super.resetData(); + + this.clipFactor = 0.0; + this.tileDivisor = 8; + } + + reAddData(annotation) { + super.reAddData(annotation); + + this.fromRedo = true; + this.clipFactor = annotation.value.data['clipFactor']; + this.tileDivisor = annotation.value.data['tileDivisor']; + this.annotation.id = annotation.id; + this.onApply(); + } +} diff --git a/web/client/src/app/iris-display/tools/tool-components/brightness.tool.ts b/web/client/src/app/iris-display/tools/tool-components/brightness.tool.ts new file mode 100644 index 0000000..71c9df0 --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/brightness.tool.ts @@ -0,0 +1,107 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; +import { Annotation, AnnotationType } from '../../../types/annotation'; + +@Component({ + selector: 'brightness-tool', + template: ` + + Brightness: {{ brightness }} +
+ + + +
+
+ `, + styles: ['div.slider-wrapper { display: flex }'] +}) +export class BrightnessTool extends IrisDrawingTool implements RequiredMembers { + name = 'Brightness'; // name cannot be static, conflicts with Function.name + static icon = 'fa-sun'; + static id = 'Brightness'; + static type: IrisToolType = IrisToolType.IMAGE; + brightness = 50; + + _updating = false; // reference to the active Iris Raster + + protected _layerKey = 'iris'; + annotation: Annotation; + fromRedo = false; + + constructor(protected toolManager: ToolManagerService) { + super(toolManager); + } + + onToolInit() { + this.toolManager.createClone(); + this.annotation = new Annotation(BrightnessTool.id, AnnotationType.BRIGHTNESS, BrightnessTool.icon); + this.annotation.value = { + points: undefined, + data: {adjustment: 0} + }; + } + + onChange() { + if (this.fromRedo) { + this.toolManager.createClone(); + } + this.userParentGroupId = this.toolManager.userParentGroupId; + if (this._updating) { + return false; + } + + this.toolChange$.next('Brightness'); + + this._updating = true; + + let imageData = this.toolManager._irisClone.getImageData(null); + + // brightness range [-100, 100] (per algorithm used) + const adjustment = (this.brightness * 2) - 100; + + //we hold onto the reference of the annotation for the duration that the user wishes to change the + //brightness + this.annotation.value.data['adjustment'] = adjustment; + + imageData = this.coreFunctions.adjustBrightness(imageData, adjustment); + this.toolManager.iris.setImageData(imageData); + + if (!this.toolManager.annotationExists(this.annotation.id)) { + this.toolManager.checkingUserCreatedAnnotations(this.annotation, true, null, null, this.userParentGroupId, null, this.fromRedo); + } + + this._updating = false; + this.toolManager.setIrisModified(true); + } + + resetData() { + super.resetData(); + + this.toolManager.userParentGroup = undefined; + + this.brightness = 50; + } + + reAddData(annotation) { + super.reAddData(annotation); + + this.fromRedo = true; + this.brightness = (+annotation.value.data['adjustment'] + 100) / 2; + this.annotation.id = annotation.id; + + this.onChange(); + } +} diff --git a/web/client/src/app/iris-display/tools/tool-components/channel.tool.ts b/web/client/src/app/iris-display/tools/tool-components/channel.tool.ts new file mode 100644 index 0000000..b4d4fb7 --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/channel.tool.ts @@ -0,0 +1,120 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, IrisToolType, RequiredMembers } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; +import { ColorChannel } from '../../core/core-functions'; +import { Annotation, AnnotationType } from '../../../types/annotation'; + +@Component({ + template: ` + +
+ + +
+
+ + +
+
+ + +
+
+ ` +}) +export class ChannelTool extends IrisDrawingTool implements RequiredMembers { + name = 'Channel'; + static icon = 'fa-chart-bar'; + static id = 'Channel'; + static type: IrisToolType = IrisToolType.IMAGE; + annotation: Annotation = new Annotation(ChannelTool.id, AnnotationType.CHANNEL, ChannelTool.icon); + idSuffix: string; + beforeId: string; + parentAnno: Annotation; + isOldAnnot: boolean; + fromRedo = false; + + constructor(protected toolManager: ToolManagerService) { + super(toolManager); + } + + onToolInit() { + this.isOldAnnot = false; + const channelAnnotations = this.toolManager.getAnnotations(AnnotationType.CHANNEL); + + if (channelAnnotations.length != 0) { + this.isOldAnnot = true; + this.annotation = channelAnnotations[0]; + + [this.parentAnno, this.beforeId] = this.toolManager.preparingRemoval(this.annotation); + + this.toolManager.removeItem(channelAnnotations[0].id, true); + } + this.toolManager.createClone(); + this.idSuffix = this.toolManager.sourceName; + + if (channelAnnotations.length != 0) { + this.onClick(this.annotation.value.data['channel']); + } + } + + onClick(color) { + if (this.fromRedo) { + this.toolManager.createClone(); + } + this.userParentGroupId = this.toolManager.userParentGroupId; + const channelAnnotations = this.toolManager.getAnnotations(AnnotationType.CHANNEL); + if (channelAnnotations.length != 0) { + this.annotation = channelAnnotations[0]; + } else { + this.annotation = new Annotation(ChannelTool.id, AnnotationType.CHANNEL, ChannelTool.icon); + } + + const origData = this.toolManager._irisClone.getImageData(null); + let colorChannel = ColorChannel.BLUE; + if (color == 'Red') { + colorChannel = ColorChannel.RED; + } else if (color == 'Green') { + colorChannel = ColorChannel.GREEN; + } else if (color == 'Blue') { + colorChannel = ColorChannel.BLUE; + } + + this.annotation.value = { + points: undefined, + data: {channel: color} + }; + + const imageData = this.coreFunctions.changeColors(colorChannel, origData); + this.toolManager._iris.setImageData(imageData); + + + if (!this.toolManager.annotationExists(this.annotation.id) && this.isOldAnnot) { + this.toolManager.checkingUserCreatedAnnotations(this.annotation, true, null, this.beforeId, this.parentAnno.id, null, this.fromRedo); + } else if (!this.toolManager.annotationExists(this.annotation.id)) { + this.toolManager.checkingUserCreatedAnnotations(this.annotation, true, null, null, this.userParentGroupId, null, this.fromRedo); + } + + this.toolChange$.next('Channel'); + this.toolManager.setIrisModified(true); + } + + reAddData(annotation) { + super.reAddData(annotation); + + this.fromRedo = true; + const color = annotation.value.data['channel']; + + this.onClick(color); + } +} diff --git a/web/client/src/app/iris-display/tools/tool-components/circle.tool.ts b/web/client/src/app/iris-display/tools/tool-components/circle.tool.ts new file mode 100644 index 0000000..7c1d8be --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/circle.tool.ts @@ -0,0 +1,253 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, IrisToolType, RequiredMembers } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; +import { Point } from 'paper'; +import { Annotation, AnnotationType } from '../../../types/annotation'; + +@Component({ + template: ` + + + Click and drag to create a circle. +
+ +
+
+ ` +}) +export class CircleTool extends IrisDrawingTool implements RequiredMembers { + name = 'Circle'; + static icon = 'fa-circle'; + static id = 'Circle'; + static type: IrisToolType = IrisToolType.ANNOTATION; + tempRing: any; + startPoint: InstanceType; + movePoint: InstanceType; + movingCircle = false; + resizingCircle = false; + circleCreated = false; + newStartSet = false; + previousPoint: any; + editCircleWidth = 4; + drawCircleWidth = 2; + circleHasSize = false; + transparentColor = 'rgba(0, 0, 0, 0)'; + annotation: Annotation; + fromRedo = false; + newCenterX: any; + newCenterY: any; + radius: any; + + tempAnno: Annotation; + + constructor(protected toolManager: ToolManagerService) { + super(toolManager); + } + + onToolInit() { + this.toolManager.createClone(); + } + + onMouseUp(event) { + if (event.event.shiftKey) { + return; + } + + if (event.target.name == 'unrolled-raster') { + return; + } + + if (this.circleHasSize) { + this.movingCircle = false; + this.resizingCircle = false; + this.newStartSet = false; + this.circleCreated = true; + } + } + + onMouseDown(event) { + if (event.target.name == 'unrolled-raster') { + return; + } + + if (!this.circleCreated) { + this.startPoint = new Point(event.point.x, event.point.y); + } else { + if (this.tempRing.hitTest(event.point)) { + this.resizingCircle = true; + this.movingCircle = false; + } else if (this.tempRing.contains(event.point)) { + this.movingCircle = true; + this.previousPoint = event.point; + } else { + this.movingCircle = false; + } + } + } + + onMouseDrag(event) { + if (event.target.name == 'unrolled-raster') { + return; + } + + if (this.movingCircle) { + // Create points for start point mod + const newStartXPosition = this.startPoint.x + event.point.x - this.previousPoint.x; + const newStartYPosition = this.startPoint.y + event.point.y - this.previousPoint.y; + + this.startPoint = new Point(newStartXPosition, newStartYPosition); + + // Create points for circle center + const newXPosition = this.tempRing.center.x + event.point.x - this.previousPoint.x; + const newYPosition = this.tempRing.center.y + event.point.y - this.previousPoint.y; + + const radius = this.tempRing.radius; + + // Create new circle + const ring = this.coreFunctions.drawCircle(new Point(newXPosition, newYPosition), radius, this.tempRing.strokeColor, this.transparentColor, this.editCircleWidth); + ring['center'] = new Point(newXPosition, newYPosition); + ring['radius'] = radius; + + ring.name = 'temp'; + this.removeTemporaryRing(); + this.tempRing = ring; + this.toolManager.tempToolLayer.addChild(this.tempRing); + this.previousPoint = event.point; + } else if (!this.circleCreated) { + this.circleHasSize = true; + this.movePoint = new Point(event.point.x, event.point.y); + this.removeTemporaryRing(); + this.tempRing = this.drawRing(this.toolManager.tempDrawColor, this.editCircleWidth); + this.toolManager.tempToolLayer.addChild(this.tempRing); + } else if (this.resizingCircle) { + // Need to find opposite point on the circle + // Get distance/difference to center + // Double and set start point to opposite side point + if (!this.newStartSet) { + const xDiff = this.tempRing.center.x - event.point.x; + const yDiff = this.tempRing.center.y - event.point.y; + + const newStartX = event.point.x + (2 * xDiff); + const newStartY = event.point.y + (2 * yDiff); + + this.startPoint = new Point(newStartX, newStartY); + this.newStartSet = true; + } + + this.movePoint = event.point; + this.removeTemporaryRing(); + this.tempRing = this.drawRing(this.toolManager.tempDrawColor, this.editCircleWidth); + this.toolManager.tempToolLayer.addChild(this.tempRing); + } + } + + drawRing(color, width) { + const distance = this.startPoint.getDistance(this.movePoint, false) / 2; + const halfPointX = (this.startPoint.x + this.movePoint.x) / 2; + const halfPointY = (this.startPoint.y + this.movePoint.y) / 2; + const midPoint = new Point(halfPointX, halfPointY); + + const ring = this.coreFunctions.drawCircle(midPoint, distance, color, this.transparentColor, width); + + ring['center'] = midPoint; + ring['radius'] = distance; + + + return ring; + } + + removeTemporaryRing() { + if (this.tempRing != null) { + this.tempRing.remove(); + this.tempRing = null; + } + } + + pushCircle() { + if (this.fromRedo) { + this.toolManager.createClone(); + } + this.userParentGroupId = this.toolManager.userParentGroupId; + if (!this.fromRedo) { + this.annotation = new Annotation(CircleTool.id, AnnotationType.CIRCLE, CircleTool.icon); + this.newCenterX = this.tempRing.center.x; + this.newCenterY = this.tempRing.center.y; + this.radius = this.tempRing.radius; + } + + const center = new Point(this.newCenterX, this.newCenterY); + + const ring = this.coreFunctions.drawCircle(center, this.radius, this.toolManager.drawColor, this.transparentColor, this.toolManager.strokeWidth); + + ring['center'] = center; + ring['radius'] = this.radius; + const points = []; + points.push({ + x: this.newCenterX, + y: this.newCenterY + }); + if (this.tempAnno) { + this.annotation.value = this.tempAnno.value; + } else { + this.annotation.value = { + points, + data: { + radius: this.radius, + strokeColor: this.toolManager.drawColor, + strokeWidth: ring.strokeWidth + } + }; + } + + ring.data.annotation = this.annotation; + + const text = '(' + Math.floor(this.annotation.value.points[0].x) + ', ' + Math.floor(this.annotation.value.points[0].y) + ')'; + let label; + + if (this.toolManager.drawAnnotationLabels) { + label = this.coreFunctions.drawAnnotationText( + new Point(this.annotation.value.points[0].x, this.annotation.value.points[0].y - this.annotation.value.data['radius']), + text, + this.toolManager.annotationColor, + this.toolManager.annotationTextSize + ); + } + this.toolManager.checkingUserCreatedAnnotations(this.annotation, true, ring, null, this.userParentGroupId, label, this.fromRedo); + + this.removeTemporaryRing(); + this.complete(); + this.toolChange$.next('Circle'); + this.toolManager.setIrisModified(true); + + // reset circle stuff + this.movingCircle = false; + this.resizingCircle = false; + this.circleCreated = false; + this.circleHasSize = false; + } + + reAddData(annotation) { + super.reAddData(annotation); + + this.fromRedo = true; + this.annotation = annotation; + this.newCenterX = annotation.value.points[0].x; + this.newCenterY = annotation.value.points[0].y; + this.radius = annotation.value.data['radius']; + this.annotation.id = annotation.id; + + this.pushCircle(); + } +} diff --git a/web/client/src/app/iris-display/tools/tool-components/colorlevel.tool.ts b/web/client/src/app/iris-display/tools/tool-components/colorlevel.tool.ts new file mode 100644 index 0000000..9b0fb93 --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/colorlevel.tool.ts @@ -0,0 +1,143 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; +import { Annotation, AnnotationType } from '../../../types/annotation'; + +@Component({ + template: ` + +
+
+
+ +
+
+ -255 + + 255 +
+
+
+
+ +
+
+ -255 + + 255 +
+
+
+
+ +
+
+ -255 + + 255 +
+
+
+ +
+ `, styles: [] +}) +export class ColorLevelTool extends IrisDrawingTool implements RequiredMembers { + name = 'ColorLevel'; + static icon = 'fa-chart-pie'; + static id = 'ColorLevel'; + static type: IrisToolType = IrisToolType.IMAGE; + colorLevel = {red: 0, green: 0, blue: 0}; + annotation: Annotation; + idSuffix: string; + fromRedo = false; + + + constructor(protected toolManager: ToolManagerService) { + super(toolManager); + } + + onToolInit() { + this.idSuffix = this.toolManager.sourceName; + this.toolManager.createClone(); + this.annotation = new Annotation(ColorLevelTool.id, AnnotationType.COLOR_LEVEL, ColorLevelTool.icon); + } + + onClear() { + this.colorLevel.red = 0; + this.colorLevel.green = 0; + this.colorLevel.blue = 0; + this.changeColors(); + this.toolManager.setIrisModified(false); + } + + changeColors() { + if (this.fromRedo) { + this.toolManager.createClone(); + } + this.userParentGroupId = this.toolManager.userParentGroupId; + const imageData = this.coreFunctions.addColor({ + red: this.colorLevel.red, + green: this.colorLevel.green, + blue: this.colorLevel.blue + }, this.toolManager._irisClone.getImageData(null)); + + this.annotation.value = { + points: undefined, + data: { + red: this.colorLevel.red, + green: this.colorLevel.green, + blue: this.colorLevel.blue + } + }; + + this.toolManager.iris.setImageData(imageData); + + if (!this.toolManager.annotationExists(this.annotation.id)) { + this.toolManager.checkingUserCreatedAnnotations(this.annotation, true, null, null, this.userParentGroupId, null, this.fromRedo); + } + + this.toolManager.setIrisModified(true); + } + + resetData() { + super.resetData(); + + this.colorLevel.red = 0; + this.colorLevel.green = 0; + this.colorLevel.blue = 0; + } + + reAddData(annotation) { + super.reAddData(annotation); + + this.fromRedo = true; + this.colorLevel.red = annotation.value.data['red']; + this.colorLevel.green = annotation.value.data['green']; + this.colorLevel.blue = annotation.value.data['blue']; + this.annotation.id = annotation.id; + + this.changeColors(); + } + +} diff --git a/web/client/src/app/iris-display/tools/tool-components/contrast.tool.ts b/web/client/src/app/iris-display/tools/tool-components/contrast.tool.ts new file mode 100644 index 0000000..c9ed5b3 --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/contrast.tool.ts @@ -0,0 +1,102 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; +import { Annotation, AnnotationType } from '../../../types/annotation'; + +@Component({ + selector: 'contrast-tool', + template: ` + + Contrast: {{ contrast }} +
+ + + +
+
+ `, + styles: ['div.slider-wrapper { display: flex }'] +}) +export class ContrastTool extends IrisDrawingTool implements RequiredMembers { + name = 'Contrast'; + static icon = 'fa-adjust'; + static id = 'Contrast'; + static type: IrisToolType = IrisToolType.IMAGE; + contrast = 50; + + protected _layerKey = 'iris'; + + _updating = false; + annotation: Annotation; + fromRedo = false; + + constructor(protected toolManager: ToolManagerService) { + super(toolManager); + } + + onToolInit() { + this.toolManager.createClone(); + this.annotation = new Annotation(ContrastTool.id, AnnotationType.CONTRAST, ContrastTool.icon); + this.annotation.value = { + points: undefined, + data: { + adjustment: 0 + } + }; + } + + onChange() { + if (this.fromRedo) { + this.toolManager.createClone(); + } + this.userParentGroupId = this.toolManager.userParentGroupId; + if (this._updating) { +return false; +} + + this._updating = true; + + let imageData = this.toolManager._irisClone.getImageData(null); + + // brightness range [-100, 100] (per algorithm used) + const adjustment = (this.contrast * 2) - 100; + this.annotation.value.data['adjustment'] = adjustment; + + imageData = this.coreFunctions.changeContrast(imageData, adjustment); + this.toolManager.iris.setImageData(imageData); + + if (!this.toolManager.annotationExists(this.annotation.id)) { + this.toolManager.checkingUserCreatedAnnotations(this.annotation, true, null, null, this.userParentGroupId, null, this.fromRedo); + } + + this._updating = false; + this.toolChange$.next('Contrast'); + this.toolManager.setIrisModified(true); + } + + resetData() { + super.resetData(); + + this.contrast = 50; + } + + reAddData(annotation) { + super.reAddData(annotation); + + this.fromRedo = true; + this.contrast = (+annotation.value.data['adjustment'] + 100) / 2; + this.annotation.id = annotation.id; + + this.onChange(); + } +} diff --git a/web/client/src/app/iris-display/tools/tool-components/crop.tool.ts b/web/client/src/app/iris-display/tools/tool-components/crop.tool.ts new file mode 100644 index 0000000..852b672 --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/crop.tool.ts @@ -0,0 +1,315 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; +import { Point } from 'paper'; +import { Annotation, AnnotationType } from '../../../types/annotation'; + +@Component({ + template: ` + + + + ` +}) +export class CropTool extends IrisDrawingTool implements RequiredMembers { + name = 'Crop'; + static icon = 'fa-crop'; + static id = 'Crop'; + static type: IrisToolType = IrisToolType.IMAGE; + rect: any; + previousPoint: any; + cropped = false; + + rectangleColor = 'black'; + rectangleLineWidth = 2; + minHeightWidth = 25; + + annotation: Annotation; + topLeft: any; + width: number; + height: number; + boundsLeft: number; + boundsTop: number; + fromRedo = false; + + constructor(protected toolManager: ToolManagerService) { + super(toolManager); + } + + onToolInit() { + this.createRectangle(); + this.toolManager.createClone(); + this.annotation = new Annotation(CropTool.id, AnnotationType.CROP, CropTool.icon); + this.annotation.value = { + points: undefined, + data: { + fromPointX: undefined, + fromPointY: undefined, + width: undefined, + height: undefined, + boundsLeft: this.toolManager.iris.bounds.left, + boundsTop: this.toolManager.iris.bounds.top + } + }; + } + + createRectangle() { + const boxWidth = this.toolManager.iris.bounds.width / 2; + const boxHeight = this.toolManager.iris.bounds.height / 2; + + const newFromX = this.toolManager.iris.bounds.center.x - boxWidth / 2; + const newFromY = this.toolManager.iris.bounds.center.y - boxHeight / 2; + + const lineWidth = Math.max(this.toolManager.iris.width, this.toolManager.iris.height); + this.rectangleLineWidth = (lineWidth / 500) + 1; + const from = new Point(newFromX, newFromY); + const to = new Point(newFromX + boxWidth, newFromY + boxHeight); + + this.rect = this.coreFunctions.drawRect( + from, + to, + this.rectangleColor, + this.rectangleLineWidth + ); + this.rect.rotation = -1 * this.toolManager._project.view.rotation; + this.cropped = false; + } + + onClick() { + if (!this.cropped) { + const bottomLeft = this.rect.segments[0].point; + this.topLeft = this.rect.segments[1].point; + const topRight = this.rect.segments[2].point; + + this.width = this.topLeft.getDistance(topRight); + this.height = this.topLeft.getDistance(bottomLeft); + + if (this.width < this.minHeightWidth || this.height < this.minHeightWidth) { + window.alert('Crop box is too small. It must be at least 25 pixels x 25 pixels large.'); + return; + } + + this.boundsLeft = this.toolManager.iris.bounds.left; + this.boundsTop = this.toolManager.iris.bounds.top; + + this.completeCrop(); + } else { + this.createRectangle(); + } + this.toolManager.setIrisModified(true); + } + + completeCrop() { + if (this.fromRedo) { + this.toolManager.createClone(); + } + this.userParentGroupId = this.toolManager.userParentGroupId; + const subRaster = this.coreFunctions.getSubRaster(this.topLeft.x, + this.topLeft.y, + this.width, + this.height, + this.toolManager.iris, + this.toolManager.iris.bounds.left, + this.toolManager.iris.bounds.top + ); + this.annotation.value = { + points: undefined, + data: { + fromPointX: this.topLeft.x, + fromPointY: this.topLeft.y, + width: this.width, + height: this.height, + boundsLeft: this.boundsLeft, + boundsTop: this.boundsTop + } + }; + this.toolManager.checkingUserCreatedAnnotations(this.annotation, true, null, null, this.userParentGroupId, null, this.fromRedo); + + //get rid of the rotation and basically restart + const tempMatrix = (this.toolManager._project.view).matrix; + tempMatrix.append(tempMatrix.clone().invert()); + + this.toolManager._iris.remove(); + this.toolManager._iris = subRaster; + this.toolManager._iris.name = 'iris-raster'; + this.toolManager._iris.size = new paper.Size(this.width, this.height); + this.toolManager._iris.position = this._layer.view.center; + + this.removeRectangle(); + + this.toolChange$.next('Crop'); + super.complete(); + + this.cropped = true; + this.toolManager.setIrisModified(true); + } + + isImageTooSmall() { + const width = this.toolManager.iris.width; + const height = this.toolManager.iris.height; + + if (width < this.minHeightWidth || height < this.minHeightWidth) { + return true; + } + + return false; + } + + rectState = 'none'; + fromPoint: InstanceType; + toPoint: InstanceType; + + onMouseDown(evt) { + if (evt.event.shiftKey || evt.event.altKey || this.cropped) { + return false; + } + + const pt = evt.point; + + const bottomLeft = this.rect.segments[0].point; + const topLeft = this.rect.segments[1].point; + const topRight = this.rect.segments[2].point; + const bottomRight = this.rect.segments[3].point; + + + if (topLeft.isClose(pt, 10)) { + this.rectState = 'resizing'; + this.toPoint = pt; + this.fromPoint = bottomRight; + } else if (topRight.isClose(pt, 10)) { + this.rectState = 'resizing'; + this.toPoint = pt; + this.fromPoint = bottomLeft; + } else if (bottomRight.isClose(pt, 10)) { + this.rectState = 'resizing'; + this.toPoint = pt; + this.fromPoint = topLeft; + } else if (bottomLeft.isClose(pt, 10)) { + this.rectState = 'resizing'; + this.toPoint = pt; + this.fromPoint = topRight; + } else if (this.rect.contains(pt)) { + this.rectState = 'moving'; + this.previousPoint = pt; + document.body.style.cursor = 'grabbing'; + } else { + document.body.style.cursor = 'default'; + } + } + + onMouseUp(evt) { + if (this.cropped) { + return; + } + + this.rectState = 'none'; + } + + onMouseMove(evt) { + const pt = evt.point; + const bottomLeft = this.rect.segments[0].point; + const topLeft = this.rect.segments[1].point; + const topRight = this.rect.segments[2].point; + const bottomRight = this.rect.segments[3].point; + + if (topLeft.isClose(pt, 10) || bottomRight.isClose(pt, 10)) { + document.body.style.cursor = 'nwse-resize'; + } else if (topRight.isClose(pt, 10) || bottomLeft.isClose(pt, 10)) { + document.body.style.cursor = 'nesw-resize'; + } else if (this.rect.contains(pt)) { + document.body.style.cursor = 'grab'; + } else{ + document.body.style.cursor = 'default'; + } + } + + drawRect(from: InstanceType, to: InstanceType) { + if (this.rect != undefined) { + this.rect.remove(); + } + + from = from.transform((this.toolManager._project.view).matrix); + to = to.transform((this.toolManager._project.view).matrix); + this.rect = this.coreFunctions.drawRect( + from, + to, + this.rectangleColor, + this.rectangleLineWidth + ); + + const tempMatrix = (this.toolManager._project.view).matrix.clone().invert(); + this.rect.transform(tempMatrix); + + } + + onMouseDrag(evt) { + if (this.cropped) { + return; + } + + if (this.rectState === 'moving') { + const newXPosition = this.rect.position.x + evt.point.x - this.previousPoint.x; + const newYPosition = this.rect.position.y + evt.point.y - this.previousPoint.y; + + this.rect.position = [newXPosition, newYPosition]; + this.previousPoint = evt.point; + } else if (this.rectState === 'resizing') { + this.drawRect(this.fromPoint, evt.point); + } + } + + checkPointDistance(fromPoint, toPoint) { + let move = true; + const xDifference = fromPoint.x - toPoint.x; + const yDifference = fromPoint.y - toPoint.y; + if (Math.abs(xDifference) < 2) { + move = false; + } + + if (Math.abs(yDifference) < 2) { + move = false; + } + + return move; + } + + removeRectangle() { + if (this.rect != null) { + this.rect.remove(); + this.rect = null; + } + } + + _destroy() { + this.removeRectangle(); + super._destroy(); + } + + reAddData(annotation) { + super.reAddData(annotation); + + this.fromRedo = true; + this.topLeft = { + x: annotation.value.data['fromPointX'], + y: annotation.value.data['fromPointY'] + }; + this.width = annotation.value.data['width']; + this.height = annotation.value.data['height']; + this.boundsLeft = annotation.value.data['boundsLeft']; + this.boundsTop = annotation.value.data['boundsTop']; + this.annotation.id = annotation.id; + + this.completeCrop(); + } +} diff --git a/web/client/src/app/iris-display/tools/tool-components/eyelid.tool.ts b/web/client/src/app/iris-display/tools/tool-components/eyelid.tool.ts new file mode 100644 index 0000000..7bd11c4 --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/eyelid.tool.ts @@ -0,0 +1,281 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; +import { Path, Point, Color } from 'paper'; +import { Annotation, AnnotationType } from '../../../types/annotation'; + +@Component({ + template: ` + +
+ Click to draw points for the upper eyelid. Shift-click to continue. + +
+
+ Click to draw points for the lower eyelid. Shift-click to continue. + +
+
+ Completed Eyelid Annotations. + Press 'Redo Eyelid Annotations' to re-annotate the eyelids. + +
+
+ ` +}) +export class EyelidTool extends IrisDrawingTool implements RequiredMembers { + name = 'Eyelid'; + static icon = 'fa-smile-beam'; + static id = 'Eyelid'; + static type: IrisToolType = IrisToolType.ANNOTATION; + path: InstanceType; + smoothing: true; + cur_point: InstanceType; + move_point: InstanceType; + move_pointAdded: boolean; + point_radius = 4; + stagesComplete = 0; + circles = []; + protected canvas: HTMLCanvasElement; + + annotation: Annotation; + points = []; + fromRedo = false; + + data = { + polygons: [] + }; + + constructor(protected toolManager: ToolManagerService) { + super(toolManager); + } + + onToolInit() { + this.toolManager.createClone(); + this.annotation = new Annotation(EyelidTool.id, AnnotationType.EYELID, EyelidTool.icon); + this.annotation.value = { + points: undefined, + data: { + strokeColor: this.config['drawColor'] || this.toolManager.drawColor, + strokeWidth: this.toolManager.strokeWidth, + eyelidType: undefined + } + }; + + const eyelidAnnotations = this.toolManager.getAnnotations(AnnotationType.EYELID); + this.stagesComplete = eyelidAnnotations.length; + this.canvas = this.toolManager._project.view.element; + this.canvas.addEventListener('mouseleave', this.onMouseLeave.bind(this)); + } + + onMouseMove(event) { + if (event.event.shiftKey || event.event.altKey || !this.isEventInCanvas(event) + || event.target.name == 'unrolled-raster' || this.stagesComplete >= 2) { + return; + } + + if (this.move_point == null) { + this.move_point = new Point(0, 0); + } + + this.move_point.x = event.point.x; + this.move_point.y = event.point.y; + + if (this.path != null) { + if (this.move_pointAdded) { + this.path.segments.pop(); + } + + this.path.add(this.move_point); + this.move_pointAdded = true; + + const distance = event.point.getDistance(this.path.segments[0].point); + if (distance < 5) { + this.path.strokeColor = new Color(this.toolManager.drawColor); + } else { + this.path.strokeColor = new Color(this.toolManager.tempDrawColor); + } + } + } + + onMouseLeave(event) { + if (this.path != null) { + if (this.move_pointAdded) { + this.path.segments.pop(); + this.move_pointAdded = false; + + } + } + } + + onMouseUp(event) { + if (event.event.shiftKey) { + if (this.move_pointAdded) { + this.path.segments.pop(); + this.move_pointAdded = false; + } + this.completeCurrentAnnotation(); + return; + } + + if (event.event.altKey || event.target.name == 'unrolled-raster' + || !this.isEventInCanvas(event) || this.stagesComplete >= 2) { + return; + } + + this.cur_point = event.point; + + if (this.path == null) { + this.path = new Path(); + this.toolManager.tempToolLayer.addChild(this.path); + } + + this.path.strokeColor = new Color(this.toolManager.tempDrawColor); + this.path.strokeWidth = this.toolManager.strokeWidth; + + if (this.move_pointAdded) { + this.path.segments.pop(); + this.move_pointAdded = false; + } + + this.path.add(this.cur_point); + + this.drawCircle(this.cur_point); + this.toolChange$.next('Eyelid'); + } + + isEventInCanvas(event) { + return true; + } + + completeCurrentAnnotation() { + this.annotation = new Annotation(EyelidTool.id, AnnotationType.EYELID, EyelidTool.icon); + this.points = []; + for (const seg of this.path.segments) { + this.points.push({x: seg.point.x, y: seg.point.y}); + } + this.annotation.value = { + points: this.points, + data: { + strokeColor: this.config['drawColor'] || this.toolManager.drawColor, + strokeWidth: this.toolManager.strokeWidth, + eyelidType: this.stagesComplete == 0 ? 'Upper' : 'Lower' + } + }; + + this.completeEyelid(); + } + + completeEyelid() { + if (this.fromRedo) { + this.toolManager.createClone(); + } + this.userParentGroupId = this.toolManager.userParentGroupId; + const polygon = this.coreFunctions.drawPolygon(this.annotation.value.points, this.annotation.value.data['strokeColor'], + this.annotation.value.data['strokeWidth'], this.annotation.value.data['fillColor'], this.annotation.value.data['fillAlpha'], false); + polygon.data.annotation = this.annotation; + polygon.closed = false; + + const text = '(' + Math.floor(this.points[0]['x']) + ', ' + Math.floor(this.points[0]['y']) + ')'; + let label; + + if (this.toolManager.drawAnnotationLabels) { + label = this.coreFunctions.drawAnnotationText( + new Point(this.points[0]), + text, + this.toolManager.annotationColor, + this.toolManager.annotationTextSize + ); + } + + this.toolManager.checkingUserCreatedAnnotations(this.annotation, true, polygon, null, this.userParentGroupId, label, this.fromRedo); + + this.removePath(); + this.path = null; + this.removeCircles(); + this.removePath(); + this.toolChange$.next('Eyelid'); + this.toolManager.setIrisModified(true); + this.stagesComplete++; + + if (this.stagesComplete >= 2) { + this.completeAnnotating(); + } + } + + completeAnnotating() { + this.toolChange$.next('Eyelid'); + this.toolManager.setIrisModified(true); + this.complete(); + } + + resetEyelidAnnotations() { + this.toolManager.removeAnnotations(AnnotationType.EYELID); + this.stagesComplete = 0; + this.removeCircles(); + this.removePath(); + this.toolChange$.next('Eyelid'); + this.toolManager.setIrisModified(true); + } + + + drawCircle(point) { + const zoom = this.toolManager._iris.view.zoom; + let rad = this.point_radius / zoom; + if (rad < 1) { + rad = 1; + } else if (rad > this.point_radius) { + rad = this.point_radius; + } + + const myCircle = new Path.Circle({ + center: this.cur_point, + radius: rad + }); + myCircle.strokeColor = new Color(this.toolManager.tempDrawColor); + myCircle.fillColor = myCircle.strokeColor; + this.toolManager.tempToolLayer.addChild(myCircle); + this.circles.push(myCircle); + } + + removeCircles() { + for (const item of this.circles) { + item.remove(); + } + this.circles = []; + } + + removePath() { + if (this.path != null) { + this.path.remove(); + this.path = null; + } + } + + reAddData(annotation) { + super.reAddData(annotation); + + this.fromRedo = true; + this.points = annotation.value.points; + this.annotation = annotation; + + this.completeEyelid(); + } + +} diff --git a/web/client/src/app/iris-display/tools/tool-components/findiris.tool.ts b/web/client/src/app/iris-display/tools/tool-components/findiris.tool.ts new file mode 100644 index 0000000..9be29f8 --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/findiris.tool.ts @@ -0,0 +1,1129 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; +import { Rectangle, Point } from 'paper'; + +class ImageArray { + public data: Uint8ClampedArray; + public width = 0; + public height = 0; + + public getPixel(x, y) { + const tempx = Math.floor(x); + const tempy = Math.floor(y); + return this.data[tempx + (tempy * this.width)]; + } +} + +class ImageNumberArray { + public data: Array; + public width = 0; + public height = 0; + + public getPixel(x, y) { + const tempx = Math.floor(x); + const tempy = Math.floor(y); + return this.data[tempx + (tempy * this.width)]; + } + +} + +@Component({ + template: ` + + ` +}) +export class FindIrisTool extends IrisDrawingTool implements RequiredMembers { + + static icon = 'fa-eye'; + static id = 'FindIris'; + static type: IrisToolType = IrisToolType.ANNOTATION; + name = 'FindIris'; //this needs to match the name in the review config json + + maxsegs = 256; + MaxR = 250; + trigLength = this.maxsegs * this.MaxR * 2; + trigVals = new Array(this.trigLength); + + trigPos = (i, j, h) => (i * this.MaxR * 2) + (j * 2) + h; + + edgeArrayLength = 0; + edgeValBin: ImageNumberArray = new ImageNumberArray(); + pupilEdgeBin: ImageNumberArray = new ImageNumberArray(); + + vertEdge: ImageArray; + + IrisCenterX = 0; + IrisCenterY = 0; + IrisRadius = 0; + + RoughPcx = 0; + RoughPcy = 0; + RoughPrad = 0; + RoughPdiam = 0; + + constructor( + protected toolManager: ToolManagerService + ) { + super(toolManager); + } + + onToolInit() { + this.initMathTables(); + const canvas = this.toolManager._project.view.element; + const imageData: ImageData = this.toolManager._iris.getImageData(new Rectangle(0, 0, canvas.width, canvas.height)); + + const ret = this.findIris(imageData); + + const ctx = this.toolManager._iris.canvas.getContext('2d', {willReadFrequently: true}); + ctx.beginPath(); + ctx.arc(this.IrisCenterX, this.IrisCenterY, this.IrisRadius, 0, 2 * Math.PI); + ctx.stroke(); + + ctx.beginPath(); + ctx.arc(Math.floor(this.RoughPcx), Math.floor(this.RoughPcy), Math.floor(this.RoughPrad), 0, 2 * Math.PI); + ctx.stroke(); + } + + findIris(imageData: ImageData): ImageData { + const grayData = this.grayScaleImage(imageData); + + const dsfactor = 4; + const dsImage = this.downScale(grayData, imageData.width, imageData.height, dsfactor); + const edgeRawBin = this.edgeMap(dsImage.data, dsImage.width, dsImage.height, dsfactor); + this.vertEdge = this.getVertEdges(dsImage.data, dsImage.width, dsImage.height); + this.findIrisCenter(dsImage.width, dsImage.height, imageData, dsImage, this.vertEdge, this.edgeValBin); + + const tempDsImage = new ImageArray(); + tempDsImage.data = dsImage.data; + tempDsImage.width = dsImage.width; + tempDsImage.height = dsImage.height; + this.findPupilCenter(this.edgeValBin, this.IrisCenterX, this.IrisCenterY, this.IrisRadius * 2, tempDsImage); + + const rgbData = this.grayToRgb(this.vertEdge.data); + const data = new ImageData(rgbData, this.vertEdge.width, this.vertEdge.height); + + return data; + } + + grayToRgb(imageData: Uint8ClampedArray): Uint8ClampedArray { + const rgbaLength = 4; + const ret = new Uint8ClampedArray(imageData.length * rgbaLength); + + for (let x = 0; x < imageData.length; x++) { + const xret = x * rgbaLength; + ret[xret] = imageData[x]; + ret[xret + 1] = imageData[x]; + ret[xret + 2] = imageData[x]; + ret[xret + 3] = 255; + } + + return ret; + } + + grayScaleImage(imageData: ImageData): Uint8ClampedArray { + //the data for the image is in rgba order + //and needs to be brought down to 8 bit grayscale + const rgbaLength = 4; + const ret = new Uint8ClampedArray(imageData.data.length / rgbaLength); + + for (let i = 0; i < imageData.data.length; i += 4) { + const r = i / 4; + ret[r] = (imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2]) / 3; + } + + return ret; + } + + //goes through original image and downscales the image by taking a box of + //pixels and scaling it down to one point + downScale(imageData: Uint8ClampedArray, imageWidth: number, imageHeight: number, downsizescale: number) { + let origx = 0; + let origy = 0; + + const averageboxsize = downsizescale * downsizescale; + + const dswidth = Math.floor(imageWidth / downsizescale); + const dsheight = Math.floor(imageHeight / downsizescale); + const ret = new Uint8ClampedArray(dswidth * dsheight); + + for (let indx = 0; indx < dswidth; indx++) { + origx = indx * downsizescale; + for (let indy = 0; indy < dsheight; indy++) { + origy = indy * downsizescale; + // for each pixel in the downsized image... + // get the average intensity over the downsizescale*downsizescale + // group of original pixels + let dspoint = 0; + for (let indxx = 0; indxx < downsizescale; indxx++) { + for (let indyy = 0; indyy < downsizescale; indyy++) { + let position = (indyy + origy) * imageWidth; + position += indxx + origx; + dspoint += imageData[position]; + } + } + + ret[indx + (indy * dswidth)] = dspoint / averageboxsize; + } + } + + return {data: ret, width: dswidth, height: dsheight}; + } + + // imageData - downscaled image + // imageWidth - downscaled image width + // imageHeight - downscaled image height + // downsizescale - scale used to downscale image + edgeMap(imageData: Uint8ClampedArray, imageWidth: number, imageHeight: number, downsizescale: number) { + + this.edgeArrayLength = Math.floor(imageHeight * imageWidth); + + this.edgeValBin.data = new Array(this.edgeArrayLength); + this.edgeValBin.width = imageWidth; + this.edgeValBin.height = imageHeight; + + this.pupilEdgeBin.data = new Array(this.edgeArrayLength); + this.pupilEdgeBin.width = imageWidth; + this.pupilEdgeBin.height = imageHeight; + + const edgeRawBin = new Uint8ClampedArray(this.edgeArrayLength); + + for (let i = 0; i < this.edgeArrayLength; i++) { + this.edgeValBin.data[i] = 0; + this.pupilEdgeBin.data[i] = 0; + } + + const imagePosition = (x, y) => x + (y * imageWidth); + + const isGreater = false; + for (let indy = downsizescale; indy < imageHeight - 1 - downsizescale; indy++) { + for (let kdx = 0; kdx < downsizescale; kdx++) { + // assign 0 edge intensity values to a 4 pixel wide boundary on the + // x axis note that this equates to a 16 pixel boundary on the + // original image since it has been downsized by factor of 4 + this.edgeValBin.data[imagePosition(kdx, indy)] = 0; + this.edgeValBin.data[imagePosition(imageWidth - downsizescale - 1 + kdx, indy)] = 0; + + this.pupilEdgeBin.data[imagePosition(kdx, indy)] = 0; + this.pupilEdgeBin.data[imagePosition(imageWidth - downsizescale - 1 + kdx, indy)] = 0; + + edgeRawBin[imagePosition(kdx, indy)] = 0; + edgeRawBin[imagePosition(imageWidth - downsizescale - 1 + kdx, indy)] = 0; + }//end of for + + for (let indx = downsizescale; indx < imageWidth - downsizescale; indx++) { + this.edgeValBin.data[imagePosition(indx, indy)] = 0; + this.pupilEdgeBin.data[imagePosition(indx, indx)] = 0; + + for (let jdx = 0; jdx < downsizescale; jdx++) { + // Takes 4 pixels to either side of the trial pixel + // Note that when this is done on a quarter downsized image - it is + // a 16 pixel wide average difference + + let imgpt1 = imageData[imagePosition(indx - jdx - 1, indy)]; + let imgpt2 = imageData[imagePosition(indx + jdx, indy)]; + + imgpt1 = imgpt1 > 200 ? 200 : imgpt1; + imgpt2 = imgpt2 > 200 ? 200 : imgpt2; + + this.edgeValBin.data[imagePosition(indx, indy)] += (imgpt1 - imgpt2); + + }//end of for loop + + if (this.edgeValBin.data[imagePosition(indx, indy)] < 0) { + this.edgeValBin.data[imagePosition(indx, indy)] *= -1; + } + + if (this.edgeValBin.data[imagePosition(indx, indy)] > 140) { + this.edgeValBin.data[imagePosition(indx, indy)] = 140; + } + + // Edge differences greater than 140 are likely due to + // specularities, not iris edges + this.edgeValBin.data[imagePosition(indx, indy)] /= 3; + edgeRawBin[imagePosition(indx, indy)] = this.edgeValBin.data[imagePosition(indx, indy)]; + + }//end of for + + }//end of for + + return { + data: edgeRawBin, + width: imageWidth, + height: imageHeight + }; + + }//end of edgemap + + + getVertEdges(dsImage: Uint8ClampedArray, imageWidth: number, imageHeight: number) { + + // Go horizontally across the downsized image and get average intensity + // differences along an 8 pixel vertical edge + // centered on the current horizontal point on each line + + const boxSize = 4; + const vertEdge = new Uint8ClampedArray(dsImage.length); + + const imagePosition = (x, y) => x + (y * imageWidth); + for (let idx8 = 0; idx8 < imageWidth; idx8++) { + // Assign zero vertical edge values in the first 4 vertical lines + for (let kdy = 0; kdy < boxSize; kdy++) { + // assign 0 edge intensity values to a 4 pixel wide boundary on the + // x axis note that this equates to a 16 pixel boundary on the + // original image since it has been downsized by factor of 4 + + vertEdge[imagePosition(idx8, kdy)] = 0; + vertEdge[imagePosition(idx8, imageHeight - kdy - 1)] = 0; + }//end of for + + for (let jdy8 = boxSize; jdy8 < imageHeight - (2 * boxSize); jdy8++) { + let pointVal = dsImage[imagePosition(idx8, jdy8 - 2)] + dsImage[imagePosition(idx8, jdy8 - 1)]; + pointVal = pointVal - dsImage[imagePosition(idx8, jdy8 + 1)] - dsImage[imagePosition(idx8, jdy8)]; + + pointVal = pointVal < 0 ? pointVal * -1 : pointVal; + pointVal = pointVal > 255 ? 255 : pointVal; + + vertEdge[imagePosition(idx8, jdy8)] = pointVal; + pointVal = pointVal & 0x000000ff; + pointVal = pointVal > 20 ? 20 : pointVal; + vertEdge[imagePosition(idx8, jdy8)] = pointVal; + }//end of for + } + + const ret = new ImageArray(); + ret.data = vertEdge; + ret.width = imageWidth; + ret.height = imageHeight; + return ret; + }//end of getVertEdges + + initMathTables() { + let anglesegnum = 0.0; + let radiuslength = 0.0; + const halfmaxsegs = this.maxsegs / 2; + const seg90 = this.maxsegs / 4; + const seg180 = this.maxsegs / 2; + let dsin = 0; + let dcos = 0; + const singlesegment = 6.28318530718 / this.maxsegs; //magic number? + + + for (let i = 0; i < this.trigLength; i++) { + this.trigVals[i] = 0; + } + + + for (anglesegnum = 0; anglesegnum < seg90 + 1; anglesegnum++) { + // calculate sine and cosines + dsin = Math.sin(singlesegment * anglesegnum); + dcos = Math.cos(singlesegment * anglesegnum); + for (radiuslength = 0; radiuslength < this.MaxR + 1; radiuslength++) { + // get x axis orinals in image array for a given angle and distance + // from center + if (dcos >= 0) { + this.trigVals[this.trigPos(anglesegnum, radiuslength, 0)] = Math.floor(dcos * radiuslength + 0.5); + } else { + this.trigVals[this.trigPos(anglesegnum, radiuslength, 0)] = Math.ceil(dcos * radiuslength - 0.5); + } + this.trigVals[this.trigPos(halfmaxsegs - anglesegnum, radiuslength, 0)] = -1 * this.trigVals[this.trigPos(anglesegnum, radiuslength, 0)] - 1; + this.trigVals[this.trigPos(halfmaxsegs + anglesegnum, radiuslength, 0)] = -1 * this.trigVals[this.trigPos(anglesegnum, radiuslength, 0)] - 1; + this.trigVals[this.trigPos(this.maxsegs - anglesegnum, radiuslength, 0)] = this.trigVals[this.trigPos(anglesegnum, radiuslength, 0)]; + + // now get y axis ordinal in image array for a given angle and + // distance from center + if (dsin >= 0) { + this.trigVals[this.trigPos(anglesegnum, radiuslength, 1)] = Math.floor(dsin * radiuslength + 0.5); + } else { + this.trigVals[this.trigPos(anglesegnum, radiuslength, 1)] = Math.ceil(dsin * radiuslength - 0.5); + } + this.trigVals[this.trigPos(halfmaxsegs - anglesegnum, radiuslength, 1)] = this.trigVals[this.trigPos(anglesegnum, radiuslength, 1)]; + this.trigVals[this.trigPos(halfmaxsegs + anglesegnum, radiuslength, 1)] = -1 * this.trigVals[this.trigPos(anglesegnum, radiuslength, 1)] - 1; + this.trigVals[this.trigPos(this.maxsegs - anglesegnum, radiuslength, 1)] = -1 * this.trigVals[this.trigPos(anglesegnum, radiuslength, 1)]; + } // radius + } + // special cases just to keep from referencing out-of-bounds values at this.MaxR + // since defined origin at lower left corner of (0,0) + this.trigVals[this.trigPos(0, this.MaxR, 0)] = this.MaxR - 1; + this.trigVals[this.trigPos(seg90, this.MaxR, 1)] = this.MaxR - 1; + this.trigVals[this.trigPos(halfmaxsegs, this.MaxR, 0)] = -this.MaxR; + this.trigVals[this.trigPos(this.maxsegs - seg90, this.MaxR, 1)] = -this.MaxR; + this.trigVals[this.trigPos(this.maxsegs, this.MaxR, 0)] = this.MaxR - 1; + } + + findIrisCenter(imageWidth: number, imageHeight: number, originalImage: ImageData, dsImage, vertEdge: any, edgeValBin: any) { + + // Version 2.2.1 change initialize darkiriscenter to zero + let darkiriscenter = 0; + let bestdarkiriscenter = 0; + + const iriscenterpt = new Array>(224); + for (let i = 0; i < 224; i++) { + iriscenterpt[i] = new Point(0, 0); + } + + // assume that Edgevalbin from above is used + // remember that image coordinants are from top left to bottom right + // MinRtrial = 35; + // Reminder that since this is on a half-sized image... + // correlates with 150 iris diameter min MaxRtrial = 100; // Max trial + // radius for 320x240 image... Max trial diameter is 400 on full-sized + // 640x480 image LowerSegTrial = 24; is the number of 360/256 degree + // segments below the horizontal axis of the iris UpperSegTrial = 12; is + // the number of segments above the horizontal axis of the iris + + // Version 2.2.1 Change ... LowerSegTrial and UpperSegTrial are globals... + // Reset these values due to conflict with Fine + // Iris finding routine. + const LowerSegTrial = 47; + const UpperSegTrial = 40; + + let MaxSegTrial = UpperSegTrial; + if (LowerSegTrial > MaxSegTrial) { + MaxSegTrial = LowerSegTrial; + } + + let bestresponse = 0; + let RoughIrisRadius = 0; + let RoughIriscx = 0; + let RoughIriscy = 0; + // set up the array of circular points close to the center to check for dark + // areas. set up the upper right quadrant and copy to the others + let ctr = 0; + let rdxx = 0; + let jdxx = 0; + for (rdxx = 2; rdxx < 17; + rdxx += + 2) { + // makes points up to 16 pixel (downsized) radius (8 samples) + // equivalent pupil diameter is 16*2 * 4 = 128 + for (jdxx = 0; jdxx < 61; + jdxx += + 10) { + // takes 10 angle segment increments up to 60 ( 7 samples ) + // creates 56 points in upper right trig quadrant (in relative + // coordinates, not image coords) + iriscenterpt[ctr].x = this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + iriscenterpt[ctr].y = this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + // creates additional 56 points in lower right, upper left and lower + // left quadrants + iriscenterpt[ctr + 56].x = this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + iriscenterpt[ctr + 56].y = -1 * this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + iriscenterpt[ctr + 112].x = -1 * this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + iriscenterpt[ctr + 112].y = this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + iriscenterpt[ctr + 168].x = -1 * this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + iriscenterpt[ctr + 168].y = -1 * this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + ctr++; + } + } + let maxIrisFindRightEdgeResponse = 0; + let maxIrisFindLeftEdgeResponse = 0; + let maxVertEdgeResponse = 0; + + let rdx = 0; + let idx = 0; + let idy = 0; + let jdx = 0; + let MaxRelX = 0; + let MinRelY = 0; + let MaxRelY = 0; + let rightedgepointsused = 0; + let leftedgepointsused = 0; + let trialresponse = 0; + let leftedgeresponse = 0; + let rightedgeresponse = 0; + let vertedgeresponse = 0; + let ptdarkval = 0; + let trialx = 0; + let trialy = 0; + let ictr5 = 0; + let srx = 0; + let suy = 0; + let slx = 0; + let sly = 0; + const MaxRtrial = 42; + const MinRtrial = 28; + + const getImagePosition = (imgData, x, y) => x + (y * imgData.width); + const counter = 0; + + for (rdx = MinRtrial; rdx < MaxRtrial; rdx++, rdx++) { + // calculate width and height of the test area for a given rdx,jdx + MaxRelX = rdx; // test cannot include X coordinants within the trial radius + MinRelY = this.trigVals[this.trigPos(LowerSegTrial, rdx, 1)]; + MaxRelY = this.trigVals[this.trigPos(UpperSegTrial, rdx, 1)]; + // remember that these are opposite image coords + for (idx = imageWidth * 3 / 10; idx < imageWidth * 7 / 10; idx++, idx++) { + for (idy = imageHeight * 3 / 10; idy < imageHeight * 7 / 10; idy++, idy++) { + rightedgepointsused = 0; + leftedgepointsused = 0; + trialresponse = 0; + leftedgeresponse = 0; + rightedgeresponse = 0; + vertedgeresponse = 0; + darkiriscenter = 0; + ptdarkval = 0; + trialx = 0; + trialy = 0; + + for (ictr5 = 0; ictr5 < 224; ictr5++) { + + // calculate "Dark Center response" for the current idx,idy + // note that the grid radius is always 5,10,15,20,25 + trialx = idx + iriscenterpt[ictr5].x; + trialy = idy - iriscenterpt[ictr5].y; + + if (trialx > 0 + && trialx < imageWidth + && trialy < imageHeight + && trialy > 0) { + + ptdarkval = dsImage.data[getImagePosition(dsImage, Math.floor(trialx), Math.floor(trialy))]; + ptdarkval = (55 - ptdarkval); + // ptdarkval = (130 - ptdarkval); gives too much credit + // in low contrast images to dark gray iris + if (ptdarkval < 0) { + ptdarkval = 0; + } + darkiriscenter += ptdarkval; + } + }//end of for + + for (jdx = 0; jdx < MaxSegTrial; jdx++) { + // right extent + srx = idx + this.trigVals[this.trigPos(jdx, rdx, 0)]; + // upper extent - remember lower y is upper + suy = idy - this.trigVals[this.trigPos(jdx, rdx, 1)]; + // left extent + slx = idx - this.trigVals[this.trigPos(jdx, rdx, 0)]; + // lower extent + sly = idy + this.trigVals[this.trigPos(jdx, rdx, 1)]; + + if ((srx + 4 < imageWidth - 1) && (srx - 2 >= 0)) { + + if ((suy >= 4) && (jdx < UpperSegTrial)) { + + rightedgeresponse += this.edgeValBin.getPixel(srx, suy); + vertedgeresponse += this.vertEdge.getPixel(srx, suy); + rightedgeresponse += this.edgeValBin.getPixel(srx - 1, suy); + vertedgeresponse += this.vertEdge.getPixel(srx - 1, suy); + rightedgeresponse += this.edgeValBin.getPixel(srx + 1, suy); + vertedgeresponse += this.vertEdge.getPixel(srx + 1, suy); + rightedgepointsused++; + } + + if ((sly < imageHeight - 3) && (jdx < LowerSegTrial)) { + + rightedgeresponse += this.edgeValBin.getPixel(srx, sly); + vertedgeresponse += this.vertEdge.getPixel(srx, sly); + rightedgeresponse += this.edgeValBin.getPixel(srx - 1, sly); + vertedgeresponse += this.vertEdge.getPixel(srx - 1, sly); + rightedgeresponse += this.edgeValBin.getPixel(srx + 1, sly); + vertedgeresponse += this.vertEdge.getPixel(srx + 1, sly); + rightedgepointsused++; + } + } + + // Left Edge response + if ((slx - 2 >= 0) && (srx + 4 < imageWidth - 1)) { + if ((suy >= 4) && (jdx < UpperSegTrial)) { + leftedgeresponse += this.edgeValBin.getPixel(slx, suy); + vertedgeresponse += this.vertEdge.getPixel(slx, suy); + leftedgeresponse += this.edgeValBin.getPixel(slx - 1, suy); + vertedgeresponse += this.vertEdge.getPixel(slx - 1, suy); + leftedgeresponse += this.edgeValBin.getPixel(slx + 1, suy); + vertedgeresponse += this.vertEdge.getPixel(slx + 1, suy); + leftedgepointsused++; + } + if ((sly < imageHeight - 1) && + (jdx < LowerSegTrial)) { + // lower left edge response + leftedgeresponse += this.edgeValBin.getPixel(slx, sly); + vertedgeresponse += this.vertEdge.getPixel(slx, sly); + leftedgeresponse += this.edgeValBin.getPixel(slx - 1, sly); + vertedgeresponse += this.vertEdge.getPixel(slx - 1, sly); + leftedgeresponse += this.edgeValBin.getPixel(slx + 1, sly); + vertedgeresponse += this.vertEdge.getPixel(slx + 1, sly); + leftedgepointsused++; + } + } + } // end of loop for angle segments to be added up + // weighting factor for darkiriscenter + darkiriscenter = darkiriscenter * 105 / 10; // 100 used in test - works with "dark threshold" of 55 + vertedgeresponse = vertedgeresponse * 3 / 10; + trialresponse = leftedgeresponse * 15 / 10 + + rightedgeresponse * 15 / 10 + + vertedgeresponse + + darkiriscenter; + if (trialresponse > bestresponse) { + bestresponse = trialresponse; + RoughIriscx = idx * 4; + RoughIriscy = idy * 4; + RoughIrisRadius = rdx * 4; + this.IrisCenterX = idx * 4; + this.IrisCenterY = idy * 4; + this.IrisRadius = rdx * 4; + maxIrisFindRightEdgeResponse = rightedgeresponse; + maxIrisFindLeftEdgeResponse = leftedgeresponse; + maxVertEdgeResponse = vertedgeresponse; + bestdarkiriscenter = darkiriscenter; + } + } // end of y loop + } // end of x loop + } // end of R loop + // check for strong vertical edges along the iris-sclera boundary as an + // indicator of occlusions + this.findFineIris(originalImage, imageWidth * 4, imageHeight * 4, this.IrisCenterX, this.IrisCenterY, this.IrisRadius * 2); + } + + findFineIris(originalImage: ImageData, imageWidth: number, imageHeight: number, roughIxc: number, roughIyc: number, roughIdiam) { + + // set the bounds of the search area based upon rough locations + const MinRfine = roughIdiam / 2 - 4; + let MaxRfine = roughIdiam / 2 + 4; + if (MaxRfine > this.MaxR) { + MaxRfine = this.MaxR; + } + const MinIxcfine = roughIxc - 4; + const MaxIxcfine = roughIxc + 4; + const MinIycfine = roughIyc - 4; + const MaxIycfine = roughIyc + 4; + const bestresponse = 0; + const leftresponse = 0; + const rightresponse = 0; + let wf = 1; + const TanPts = new Array>(33); + for (let i = 0; i < TanPts.length; i++) { + TanPts[i] = new Point(0, 0); + } + + let rightedgepointsused = 0; + let leftedgepointsused = 0; + let trialresponse = 0; + let leftedgeresponse = 0; + let rightedgeresponse = 0; + const maxpointresponse = 2000; + let srx = 0; + let suy = 0; + let slx = 0; + let sly = 0; + let idx = 0; + let jdx = 0; + let idy = 0; + let rctr = 0; + let jjdx = 0; + + // 47 works 256 / 4 = 64 + const LowerSegTrial = 40; + // 35 works higher fails on droopy eyelids + // cause failure lower fails on large pupils + const UpperSegTrial = 30; + + //magic number pulled from original that is supposed to be the original value of LowerSegTrial + const uppersegpointresponses = new Array(47); + const maxuppersegresponses = new Array(47); + for (let i = 0; i < 47; i++) { + uppersegpointresponses[i] = 0; + maxuppersegresponses[i] = 0; + } + + const getImagePosition = (imgData, x, y) => x + (y * imgData.width); + for (let rdx = MinRfine; rdx < MaxRfine; rdx++) { + // calculate width and height of the test area for a given rdx, jdx + for (idx = MinIxcfine; idx < MaxIxcfine; idx++) { + for (idy = MinIycfine; idy < MaxIycfine; idy++) { + rightedgepointsused = 0; + leftedgepointsused = 0; + trialresponse = 0; + leftedgeresponse = 0; + rightedgeresponse = 0; + + for (jdx = 0; jdx < LowerSegTrial; jdx += 2) { + + uppersegpointresponses[jdx] = 0; + // fix for the fact that the radial will extend an + // additional 12-16 pixels! Right extent + srx = idx + this.trigVals[this.trigPos(jdx, rdx + 16, 0)]; + // Upper extent - remember lower y is upper + suy = idy - this.trigVals[this.trigPos(jdx, rdx + 16, 1)]; + // Left extent + slx = idx - this.trigVals[this.trigPos(jdx, rdx + 16, 0)]; + // Lower extent + sly = idy + this.trigVals[this.trigPos(jdx, rdx + 16, 1)]; + // weighting factor to give more weight to pixels + // closest to the exact edge + wf = 1; + + // Check to see if + // this is correct on + // the full size image + if ((srx < imageWidth) && (slx >= 0)) { + if ((suy >= 0) && (jdx < UpperSegTrial)) { + let pointresponse = 0; + for (rctr = 0; rctr < 16; rctr++, rctr++) { + wf = 4 - (rctr / 4); + const tempx = idx + this.trigVals[this.trigPos(jdx, rdx + rctr, 0)]; + const tempy = idy - this.trigVals[this.trigPos(jdx, rdx + rctr, 1)]; + + const tempx2 = idx + this.trigVals[this.trigPos(jdx, rdx - rctr, 0)]; + const tempy2 = idy - this.trigVals[this.trigPos(jdx, rdx - rctr, 1)]; + + const tempMul = originalImage.data[getImagePosition(originalImage, tempx, tempy)]; + const tempMul2 = originalImage.data[getImagePosition(originalImage, tempx2, tempx2)]; + pointresponse += wf * (tempMul - tempMul2); + } + + if (pointresponse < 0) { + pointresponse = -pointresponse; + } + + if (pointresponse > maxpointresponse) { + pointresponse = maxpointresponse; + } + + uppersegpointresponses[jdx] = pointresponse; + rightedgeresponse += pointresponse; + rightedgepointsused++; + } + + if ((sly < imageWidth) && (jdx < LowerSegTrial)) { + let pointresponse = 0; + for (rctr = 0; rctr < 16; rctr++) { + wf = 4 - (rctr / 4); + const tempx1 = idx + this.trigVals[this.trigPos(jdx, rdx + rctr, 0)]; + const tempy1 = idy + this.trigVals[this.trigPos(jdx, rdx + rctr, 1)]; + + const tempx2 = idx + this.trigVals[this.trigPos(jdx, rdx - rctr, 0)]; + const tempy2 = idy + this.trigVals[this.trigPos(jdx, rdx - rctr, 1)]; + + const tempMul = originalImage.data[getImagePosition(originalImage, tempx1, tempy1)]; + const tempMul2 = originalImage.data[getImagePosition(originalImage, tempx2, tempx2)]; + + pointresponse += wf * (tempMul - tempMul2); + // watch out for X values that go beyond the + // image + } + + if (pointresponse < 0) { + pointresponse = -pointresponse; + } + + if (pointresponse > maxpointresponse) { + pointresponse = maxpointresponse; + } + + rightedgeresponse += pointresponse; + rightedgepointsused++; + } + } + + if ((slx >= 0) && (srx <= imageWidth)) { + if ((suy >= 0) && (jdx < UpperSegTrial)) { + let pointresponse = 0; + for (rctr = 0; rctr < 16; rctr++) { + wf = 4 - (rctr / 4); + const tempx = idx - this.trigVals[this.trigPos(jdx, rdx + rctr, 0)]; + const tempy = idy - this.trigVals[this.trigPos(jdx, rdx + rctr, 1)]; + + const tempx2 = idx - this.trigVals[this.trigPos(jdx, rdx - rctr, 0)]; + const tempy2 = idy - this.trigVals[this.trigPos(jdx, rdx - rctr, 1)]; + + const tempMul = originalImage.data[getImagePosition(originalImage, tempx, tempy)]; + const tempMul2 = originalImage.data[getImagePosition(originalImage, tempx2, tempx2)]; + + pointresponse += wf * (tempMul - tempMul2); + } + + if (pointresponse < 0) { + pointresponse = -pointresponse; + } + + if (pointresponse > maxpointresponse) { + pointresponse = maxpointresponse; + } + + uppersegpointresponses[jdx] += pointresponse; + leftedgeresponse += pointresponse; + leftedgepointsused++; + } + + if ((sly < imageHeight - 1) && (jdx < LowerSegTrial)) { + // fill in logic to get the lower left response + let pointresponse = 0; + for (rctr = 0; rctr < 16; rctr++) { + wf = 4 - (rctr / 4); + const tempx1 = idx - this.trigVals[this.trigPos(jdx, rdx + rctr, 0)]; + const tempy1 = idy + this.trigVals[this.trigPos(jdx, rdx + rctr, 1)]; + + const tempx2 = idx - this.trigVals[this.trigPos(jdx, rdx - rctr, 0)]; + const tempy2 = idy + this.trigVals[this.trigPos(jdx, rdx - rctr, 1)]; + + const tempMul = originalImage.data[getImagePosition(originalImage, tempx1, tempy1)]; + const tempMul2 = originalImage.data[getImagePosition(originalImage, tempx2, tempx2)]; + pointresponse += wf * (tempMul - tempMul2); + + } + + if (pointresponse < 0) { + pointresponse = -pointresponse; + } + + if (pointresponse > maxpointresponse) { + pointresponse = maxpointresponse; + } + + leftedgeresponse += pointresponse; + leftedgepointsused++; + }//end of if + } + } // end of loop for angle segments to be added up + trialresponse = leftedgeresponse + rightedgeresponse; + + if (trialresponse > bestresponse) { + + this.IrisCenterX = idx; + this.IrisCenterY = idy; + this.IrisRadius = rdx; + + // now save the upperedgepointresponses for later use + for (jjdx = 0; jjdx < UpperSegTrial; jjdx++) { + maxuppersegresponses[jjdx] = uppersegpointresponses[jjdx]; + } + } + } // end of y loop + } // end of x loop + } // end of R loop + // Use the best diameter to calculate diagnostic metrics: + // Get Iris-Sclera GrayScale Difference values over a -60 to 45 deg arc + // along the diameter Use response value trends along the arc on left and + // right sides to determine likely eyelid locations Fill in ISGSVals[256]; + // // 0 is 0 degrees // 64 is 90 deg. //128 is 180 deg //192 is 270 deg. + const IXC = this.IrisCenterX; + const IYC = this.IrisCenterY; + const IRAD = this.IrisRadius; + + const ISGSVals = new Array(256); + for (let i = 0; i < ISGSVals.length; i++) { + ISGSVals[i] = 0; + } + + if (this.IrisRadius > this.MaxR - 16) { + console.error('Iris Radius exceeds max allowable'); + return; + } + + if (this.IrisRadius < 17) { + console.error('Invalid Iris Diameter value', this.IrisRadius); + return; + } + + let rctr2 = 0; + let outerpointval = 0; + let innerpointval = 0; + for (let jdxx = 0; jdxx < this.maxsegs; jdxx++) { + // in this array TanPts[33] is the centered on the iris edge arc + for (rctr = 0; rctr < 33; rctr++) { + TanPts[rctr].x = IXC + this.trigVals[this.trigPos(jdxx, IRAD + rctr - 16, 0)]; + TanPts[rctr].y = IYC - this.trigVals[this.trigPos(jdxx, IRAD + rctr - 16, 1)]; + } + + let pointresponse = 0; + let numradpts = 0; + for (rctr2 = 1; rctr2 < 16; rctr2++) { + if (TanPts[16 + rctr2].x > 0 + && TanPts[16 + rctr2].x < imageWidth + && TanPts[16 + rctr2].y > 0 + && TanPts[16 + rctr2].y < imageHeight) { + wf = 1; + const tempx = TanPts[16 + rctr2].x; + const tempy = TanPts[16 + rctr2].y; + const tempx2 = TanPts[16 - rctr2].x; + const tempy2 = TanPts[16 - rctr2].y; + + // weighting factor for distance from iris edge point going 16 + // pixels each direction wf = 4 - (rctr2 / 4); + outerpointval = originalImage.data[getImagePosition(originalImage, tempx, tempy)]; + innerpointval = originalImage.data[getImagePosition(originalImage, tempx2, tempy2)]; + // Version 2.2.1 Change to limit effects of single differences) + let responseint = outerpointval - innerpointval; + if (responseint > 75) { + responseint = 75; + } + pointresponse = pointresponse + responseint; + numradpts++; + } else { + pointresponse = 0; + } + } + ISGSVals[jdxx] = pointresponse; + } // end of jdxx - intensity difference for each radial segment from 0 to + // maxsegs at IXC, IYC, IRAD + // Calculate adjusted Iris-Sclera GrayScale Difference using averages of + // ISGSVals[128:138] and ISGSVals[246:0]; + let isgstotal = 0; + + // ISGSDiffMeanAvg = isgstotal / 300; // remember that each point in + // ISGSVals is 15 pixels on each side + let ptsused = 0; + for (let jctr2 = 0; jctr2 < 40; jctr2++) { + // Each point in ISGSVals is 15 pixels on each side + isgstotal = isgstotal + ISGSVals[255 - jctr2]; + ptsused++; + isgstotal = isgstotal + ISGSVals[128 + jctr2]; + ptsused++; + } + + for (let jctr3 = 0; jctr3 < 20; jctr3++) { + // Each point in ISGSVals is 15 pixels on each side + // Add response on 20 upper segments + isgstotal = isgstotal + ISGSVals[jctr3]; + ptsused++; + isgstotal = isgstotal + ISGSVals[127 - jctr3]; + ptsused++; + } + + const ISGSDiffMeanAvg = isgstotal / (ptsused * 15); // Remember that each point in ISGSVals is 15 pixels on each side + } + + + findPupilCenter(edgeData: ImageNumberArray, irisCenterX: number, irisCenterY: number, irisDiameter: number, dsImage: ImageArray) { + // Use same routine as Find Iris but restrict search to +/-0.1Idiam + // inside iris region and limit radius range to 0.8 irisd. This means + // that pupil center is restricted to within 40% of Iris diameter from + // Iris center. Also restrict trial iris radius so that it does not get + // within 8 pixels from iris edge + // the division by 4 is because the edgeData and the dsImage have been + // downscaled by 4 + const irisreduced = (irisDiameter / 4) / 10; + let roitop = (irisCenterY - irisreduced) / 4; + let roileft = (irisCenterX - irisreduced) / 4; + let roibottom = (irisCenterY + irisreduced) / 4; + let roiright = (irisCenterX + irisreduced) / 4; + let PupIcdist = 0; + let ipgsresponse = 0; + let MaxPupilEdgeResponse = 0; + let edgeresponse = 0; + const pwf = 0; + + const counter = 0; + + const pupilcenterpt = new Array>(224); + for (let i = 0; i < pupilcenterpt.length; i++) { + pupilcenterpt[i] = new Point(0, 0); + } + + let pctr = 0; + let rdxx = 0; + let jdxx = 0; + + // makes points up to 45 radius = 100 pixel diameter (8 samples) + for (rdxx = 3; rdxx < 25; rdxx += 3) { + // this is in downsized (by factor of 4) dimensions + // takes 10 angle segment increments up to 60 ( 7 samples ) + for (jdxx = 0; jdxx < 61; jdxx += 10) { + // creates 56 points in upper right trig quadrant (in relative + // coordinates, not image coords) + pupilcenterpt[pctr].x = this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + pupilcenterpt[pctr].y = this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + // creates additional 56 points in lower right, upper left and + // lower left quadrants + pupilcenterpt[pctr + 56].x = this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + pupilcenterpt[pctr + 56].y = -1.0 * this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + pupilcenterpt[pctr + 112].x = -1.0 * this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + pupilcenterpt[pctr + 112].y = this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + pupilcenterpt[pctr + 168].x = -1.0 * this.trigVals[this.trigPos(jdxx, rdxx, 0)]; + pupilcenterpt[pctr + 168].y = -1.0 * this.trigVals[this.trigPos(jdxx, rdxx, 1)]; + pctr++; + } + } + let ipgspointsused = 0; + + const PUpperSegTrial = 60; + const PLowerSegTrial = 63; + // make sure ROI is inside the image + if (roitop < 0) { + roitop = 0; + } + + if (roileft < 0) { + roileft = 0; + } + + if (roiright > edgeData.width - 1) { + roiright = edgeData.width - 1; + } + + if (roibottom > edgeData.height - 1) { + roibottom = edgeData.height - 1; + } + + let PMaxSegTrial = PUpperSegTrial; + if (PLowerSegTrial > PMaxSegTrial) { + PMaxSegTrial = PLowerSegTrial; + } + + let bestdarkpupilresponse = 0; + let darkpupilval = 0; + let bestresponse = 0; + + MaxPupilEdgeResponse = 0; + edgeresponse = 0; + let pupilCx = 0; + let pupilCy = 0; + let pupilD = 0; + let pupilRad = 0; + // for each trial radius rdx and for angle range jdx... find the best + // total response for the image + + const MaxPrtrial = (this.IrisRadius / 4) * 0.8; // Use 80% of IrisRadius + let rightedgepointsused = 0; + let leftedgepointsused = 0; + let rdx = 0; + let idx = 0; + let idy = 0; + let jdx = 0; + let ptdarkval = 0; + let trialx = 0; + let trialy = 0; + let pctr5 = 0; + let trialresponse = 0; + let leftedgeresponse = 0; + let rightedgeresponse = 0; + let srx = 0; + let suy = 0; + let slx = 0; + let sly = 0; + const MinPrtrial = 8; + for (rdx = MinPrtrial; rdx < MaxPrtrial; rdx++) { + // calculate width and height of the test area for a given rdx,jdx + for (idx = roileft; idx < roiright; idx++) { + for (idy = roitop; idy < roibottom; idy++) { + darkpupilval = 0; + ptdarkval = 0; + trialx = 0; + trialy = 0; + for (pctr5 = 0; pctr5 < 224; pctr5++) { + // calculate "Dark Center response" for the current idx, + // idy note that the grid radius is always 5,10,15,20,25 + trialx = idx + pupilcenterpt[pctr5].x; + trialy = idy - pupilcenterpt[pctr5].y; + if (trialx > 0 && trialx < edgeData.width && + trialy < edgeData.height && trialy > 0) { + ptdarkval = dsImage.getPixel(trialx, trialy); + ptdarkval = 130 - ptdarkval; + if (ptdarkval < 0) { + ptdarkval = 0; + } + ptdarkval /= 5; + darkpupilval = darkpupilval + ptdarkval; + } + } + + ipgsresponse = 0; + ipgspointsused = 0; + rightedgepointsused = 0; + leftedgepointsused = 0; + trialresponse = 0; + leftedgeresponse = 0; + rightedgeresponse = 0; + + for (jdx = 0; jdx < PMaxSegTrial; jdx++, jdx++) { + srx = idx + this.trigVals[this.trigPos(jdx, rdx, 0)]; // Right extent + suy = idy - this.trigVals[this.trigPos(jdx, rdx, 1)]; // Upper extent - + // remember lower y is upper + slx = idx - this.trigVals[this.trigPos(jdx, rdx, 0)]; // Left extent + sly = idy + this.trigVals[this.trigPos(jdx, rdx, 1)]; // Lower extent + + if ((srx + 4 < edgeData.width - 1) && (srx - 4 >= 0)) { + if ((suy >= 0) && (jdx < PUpperSegTrial)) { + rightedgeresponse += 2 * edgeData.getPixel(srx, suy); + rightedgeresponse += edgeData.getPixel(srx - 1, suy); + rightedgeresponse += edgeData.getPixel(srx + 1, suy); + rightedgepointsused++; + } + if ((sly < edgeData.height - 1) && + (jdx < PLowerSegTrial)) { + rightedgeresponse += 2 * edgeData.getPixel(srx, sly); + rightedgeresponse += edgeData.getPixel(srx - 1, sly); + rightedgeresponse += edgeData.getPixel(srx + 1, sly); + rightedgepointsused++; + } + } + if ((slx - 4 >= 0) && (srx + 4 <= edgeData.width - 1)) { + if ((suy >= 0) && (jdx < PUpperSegTrial)) { + leftedgeresponse += 2 * edgeData.getPixel(slx, suy); + leftedgeresponse += edgeData.getPixel(slx - 1, suy); + leftedgeresponse += edgeData.getPixel(slx + 1, suy); + leftedgepointsused++; + } + if ((sly < edgeData.height - 1) && + (jdx < PLowerSegTrial)) { + leftedgeresponse += 2 * edgeData.getPixel(slx, sly); + leftedgeresponse += edgeData.getPixel(slx - 1, sly); + leftedgeresponse += edgeData.getPixel(slx + 1, sly); + leftedgepointsused++; + } + } + } // end of loop for angle segments to be added up + if (leftedgepointsused + rightedgepointsused) { + edgeresponse = (leftedgeresponse + rightedgeresponse) * + 100 / + (leftedgepointsused + rightedgepointsused); + } else { + edgeresponse = 0; + } + darkpupilval *= 2; // 4 + trialresponse = edgeresponse + darkpupilval; + + + if (trialresponse > bestresponse) { + console.log('Setting pupil values', pupilCx, pupilCy, pupilRad); + bestresponse = trialresponse; + MaxPupilEdgeResponse = edgeresponse; + bestdarkpupilresponse = darkpupilval; + pupilCx = (idx - 1) * 4; + pupilCy = (idy) * 4; + pupilRad = (rdx) * 4; + pupilD = rdx * 8; + } + + { + PupIcdist = Math.sqrt(((irisCenterY - idy) * (irisCenterY - idy) + (irisCenterX - idx) * (irisCenterX - idx))); + if ((PupIcdist + rdx) > (irisDiameter / 2) * 65 / 100 || + (idy + rdx) > (irisCenterY + (irisDiameter / 2) * 65 / 100) || + (idy - rdx) < (idy - (irisCenterY / 2) * 65 / 100)) { + // combination of trial radius rdx and trial pupilCx + // (idx), pupilCy (idy) is within 85% of the edge of + // the Iris so stop the loop + idy = roibottom; + } + }//end of block + } // end of y loop + + if ((idx + rdx) > (irisCenterX + (irisDiameter / 2) * 65 / 100) || + (irisCenterX - rdx) < (idx - (irisDiameter / 2) * 65 / 100)) { + // combination of trial radius rdx and trial pupilCx (idx), + // pupilCy (idy) is within 85% of the edge of the Iris so + // stop the loop + idx = roiright; + } + } // end of x loop + } // end of R loop ROUGH PUPIL RADIUS and Center point Done + + this.RoughPcx = pupilCx; + this.RoughPcy = pupilCy; + this.RoughPrad = pupilRad; + this.RoughPdiam = pupilRad * 2; + } + +} diff --git a/web/client/src/app/iris-display/tools/tool-components/index.ts b/web/client/src/app/iris-display/tools/tool-components/index.ts new file mode 100644 index 0000000..f5744f4 --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/index.ts @@ -0,0 +1,32 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +/** + * Add both an Import and Export statement for each Tool, so the Angular-cli will track changes to the file via webpack + * (Without an Import statement, the file will NOT be re-transpiled on edits) + */ + +export * from './brightness.tool'; +export * from './contrast.tool'; +export * from './point.tool'; +export * from './crop.tool'; +export * from './mirror.tool'; +export * from './pencil.tool'; +export * from './rotate.tool'; +export * from './scale.tool'; +export * from './colorlevel.tool'; +export * from './channel.tool'; +export * from './zoom.tool'; +export * from './polygon.tool'; +export * from './circle.tool'; +export * from './sharpen.tool'; +export * from './unroll.tool'; +export * from './adaptiveContrast.tool'; +export * from './eyelid.tool'; diff --git a/web/client/src/app/iris-display/tools/tool-components/mirror.tool.ts b/web/client/src/app/iris-display/tools/tool-components/mirror.tool.ts new file mode 100644 index 0000000..e0447ad --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/mirror.tool.ts @@ -0,0 +1,91 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; +import { Annotation, AnnotationType } from '../../../types/annotation'; + +@Component({ + template: ` + + + + ` +}) +export class MirrorTool extends IrisDrawingTool implements RequiredMembers { + name = 'Mirror'; + static icon = 'fa-exchange-alt'; + static id = 'Mirror'; + static type: IrisToolType = IrisToolType.IMAGE; + annotation: Annotation; + fromRedo = false; + + constructor(protected toolManager: ToolManagerService) { + super(toolManager); + } + + onToolInit() { + this.toolManager.createClone(); + this.annotation = new Annotation(MirrorTool.id, AnnotationType.MIRROR, MirrorTool.icon); + } + + onClick(inputPosition?, annotationId?) { + if (this.fromRedo) { + this.toolManager.createClone(); + } + this.userParentGroupId = this.toolManager.userParentGroupId; + let position; + if (!!inputPosition) { + position = inputPosition; + } + + const mirrorAnnotations = this.toolManager.getAnnotations(AnnotationType.MIRROR); + if (mirrorAnnotations.length != 0) { + this.annotation = mirrorAnnotations[0]; + position = this.annotation.value.data['adjustment']; + } else { + position = 'Normal'; + this.annotation = new Annotation(MirrorTool.id, AnnotationType.MIRROR, MirrorTool.icon); + } + + if (position === 'Flipped') { + position = 'Normal'; + } else { + position = 'Flipped'; + } + + this.annotation.value = { + points: undefined, + data: { + adjustment: position + } + }; + + this.coreFunctions.mirrorImage(this.toolManager.iris); + + if (!this.toolManager.annotationExists(this.annotation.id)) { + this.toolManager.checkingUserCreatedAnnotations(this.annotation, true, null, null, this.userParentGroupId, null, this.fromRedo); + } + + this.toolChange$.next('Mirror'); + this.toolManager.setIrisModified(true); + } + + reAddData(annotation) { + super.reAddData(annotation); + + this.fromRedo = true; + const position = annotation.value.data['adjustment']; + + this.onClick(position, annotation.id); + } + +} diff --git a/web/client/src/app/iris-display/tools/tool-components/pencil.tool.ts b/web/client/src/app/iris-display/tools/tool-components/pencil.tool.ts new file mode 100644 index 0000000..274b83b --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/pencil.tool.ts @@ -0,0 +1,157 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; +import { Path, Color, Point } from 'paper'; +import { Annotation, AnnotationType } from '../../../types/annotation'; + +@Component({ + template: ` + + Hold the button to draw + + ` +}) +export class PencilTool extends IrisDrawingTool implements RequiredMembers { + name = 'Pencil'; + static icon = 'fa-pencil-alt'; + static id = 'Pencil'; + static type: IrisToolType = IrisToolType.ANNOTATION; + path: InstanceType; + smoothing: true; + data = { + type: 'pencil', + points: [] + }; + + annotation: Annotation; + fromRedo = false; + + constructor(protected toolManager: ToolManagerService) { + super(toolManager); + } + + onToolInit() { + this.toolManager.createClone(); + } + + onMouseDown(evt) { + this.annotation = new Annotation(PencilTool.id, AnnotationType.PENCIL, PencilTool.icon); + this.annotation.value = { + points: undefined, + data: { + strokeColor: this.toolManager.drawColor, + strokeWidth: this.toolManager.strokeWidth + } + }; + + if (evt.event.shiftKey || evt.event.altKey) { +return false; +} + + if (evt.target.name == 'unrolled-raster') { + return false; + } + + this.path = new Path(); + this.path.strokeColor = new Color(this.toolManager.tempDrawColor); + this.path.strokeWidth = this.toolManager.strokeWidth; + this.path.add(evt.point); + } + + onMouseDrag(evt) { + if (evt.target.name == 'unrolled-raster') { + return; + } + this.path.add(evt.point); + } + + onMouseUp(event) { + if (event.event.shiftKey || event.event.altKey) { + return; + } + + if (event.target.name == 'unrolled-raster') { + return; + } + + if (this.smoothing) {//reduce the points + this.path.simplify(); + } + + const points = []; + for (const seg of this.path.segments) { + points.push({ + x: seg.point.x, + y: seg.point.y + }); + } + this.annotation.value = { + points, + data: { + strokeColor: this.toolManager.drawColor, + strokeWidth: this.toolManager.strokeWidth + } + }; + + this.completePencil(this.annotation); + } + + completePencil(annotation: Annotation) { + if (this.fromRedo) { + this.toolManager.createClone(); + } + this.userParentGroupId = this.toolManager.userParentGroupId; + const pencil = this.coreFunctions.drawPath(annotation.value.points, annotation.value.data['strokeColor'], annotation.value.data['strokeWidth']); + pencil.data.annotation = annotation; + + const text = '(' + Math.floor(annotation.value.points[0]['x']) + ', ' + Math.floor(annotation.value.points[0]['y']) + ')'; + let label; + + if (this.toolManager.drawAnnotationLabels) { + label = this.coreFunctions.drawAnnotationText( + new Point(annotation.value.points[0]), + text, + this.toolManager.annotationColor, + this.toolManager.annotationTextSize + ); + } + + this.toolManager.checkingUserCreatedAnnotations(annotation, true, pencil, null, this.userParentGroupId, label, this.fromRedo); + + if (!this.fromRedo) { + this.data.points.push({drawObj: this.path.segments, drawShape: 'pencil'}); + this._finalItem = this.path; + + this.removePath(); + } + + this.complete(); + this.toolChange$.next('Pencil'); + this.toolManager.setIrisModified(true); + } + + removePath() { + if (this.path != null) { + this.path.remove(); + this.path = null; + } + } + + reAddData(annotation) { + super.reAddData(annotation); + + this.fromRedo = true; + + this.completePencil(annotation); + } +} diff --git a/web/client/src/app/iris-display/tools/tool-components/point.tool.ts b/web/client/src/app/iris-display/tools/tool-components/point.tool.ts new file mode 100644 index 0000000..d16fbc1 --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/point.tool.ts @@ -0,0 +1,109 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; +import { Point } from 'paper'; +import { Annotation, AnnotationType } from '../../../types/annotation'; +import { CoreFunctions } from '../../core/core-functions'; + +@Component({ + template: ` + + + ` +}) +export class PointTool extends IrisDrawingTool implements RequiredMembers { + name = 'Point'; + static icon = 'fa-map-pin'; + static id = 'Point'; + static type: IrisToolType = IrisToolType.ANNOTATION; + annotationType = AnnotationType.POINT; + pointType = 'circle'; + radius = 3; + strokeColor = '#127bdc'; + fillColor = '#333333'; + coreFunctions: CoreFunctions = new CoreFunctions(); + + fromRedo = false; + + constructor(protected toolManager: ToolManagerService) { + super(toolManager); + this.toolManager.createClone(); + } + + onMouseDown(evt) { + if (evt.event.shiftKey || evt.event.altKey) { + return false; + } + + if (evt.target.name == 'unrolled-raster') { + return; + } + + this.completePoint(evt.point); + } + + completePoint(eventPoint, annotationId?) { + if (this.fromRedo) { + this.toolManager.createClone(); + } + this.userParentGroupId = this.toolManager.userParentGroupId; + const point = this.coreFunctions.drawCircle(eventPoint, this.radius, this.toolManager.drawColor, this.toolManager.fillColor); + const annotation = new Annotation(PointTool.id, AnnotationType.POINT, PointTool.icon); + + const points = []; + points.push({ + x: point.position.x, + y: point.position.y + }); + annotation.value = { + points, + data: { + radius: this.radius, + strokeColor: this.toolManager.drawColor, + strokeWidth: 1, + fillColor: this.toolManager.fillColor + } + }; + if (!!annotationId) { + annotation.id = annotationId; + } + + + const text = '(' + Math.floor(annotation.value.points[0].x) + ', ' + Math.floor(annotation.value.points[0].y) + ')'; + let label; + + if (this.toolManager.drawAnnotationLabels) { + label = this.coreFunctions.drawAnnotationText( + new Point(annotation.value.points[0].x, annotation.value.points[0].y), + text, + this.toolManager.annotationColor, + this.toolManager.annotationTextSize + ); + } + + this.toolManager.checkingUserCreatedAnnotations(annotation, true, point, null, this.userParentGroupId, label, this.fromRedo); + + this.toolChange$.next('Pencil'); + this.toolManager.setIrisModified(true); + this._finalItem = point; + this.complete(); + } + + reAddData(annotation) { + super.reAddData(annotation); + + this.fromRedo = true; + + this.completePoint(annotation.value.points[0], annotation.id); + } +} diff --git a/web/client/src/app/iris-display/tools/tool-components/polygon.tool.ts b/web/client/src/app/iris-display/tools/tool-components/polygon.tool.ts new file mode 100644 index 0000000..ae3e9db --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/polygon.tool.ts @@ -0,0 +1,226 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; +import { Path, Point, Color } from 'paper'; +import { Annotation, AnnotationType } from '../../../types/annotation'; + +@Component({ + template: ` + + Draw points for the polygon. Select first point to complete. + + ` +}) +export class PolygonTool extends IrisDrawingTool implements RequiredMembers { + name = 'Polygon'; + static icon = 'fa-cube'; + static id = 'Polygon'; + static type: IrisToolType = IrisToolType.ANNOTATION; + path: InstanceType; + smoothing: true; + cur_point: InstanceType; + move_point: InstanceType; + move_pointAdded: boolean; + point_radius = 4; + circles = []; + data = { + polygons: [] + }; + + annotation: Annotation; + fromRedo = false; + + constructor(protected toolManager: ToolManagerService) { + super(toolManager); + this.toolManager.createClone(); + } + + onMouseMove(event) { + this.annotation = new Annotation(PolygonTool.id, AnnotationType.POLYGON, PolygonTool.icon); + this.annotation.value = { + points: undefined, + data: { + strokeColor: this.config['drawColor'] || this.toolManager.drawColor, + fillColor: this.config['fillColor'] || this.toolManager.fillColor, + strokeWidth: this.toolManager.strokeWidth, + fillAlpha: this.data['fillAlpha'] || this.defaults['fillAlpha'] + } + }; + + if (event.event.shiftKey || event.event.altKey) { + return; + } + + if (event.target.name == 'unrolled-raster') { + return; + } + + if (this.move_point == null) { + this.move_point = new Point(0, 0); + } + + this.move_point.x = event.point.x; + this.move_point.y = event.point.y; + + if (this.path != null) { + if (this.move_pointAdded) { + this.path.segments.pop(); + } + + this.path.add(this.move_point); + this.move_pointAdded = true; + + const distance = event.point.getDistance(this.path.segments[0].point); + if (distance < 5) { + this.path.strokeColor = new Color(this.toolManager.drawColor); + } else { + this.path.strokeColor = new Color(this.toolManager.tempDrawColor); + } + } + } + + onMouseUp(event) { + if (event.event.shiftKey || event.event.altKey) { + return; + } + + if (event.target.name == 'unrolled-raster') { + return; + } + + this.cur_point = event.point; + + if (this.path == null) { + this.path = new Path(); + this.toolManager.tempToolLayer.addChild(this.path); + } + + this.path.strokeColor = new Color(this.toolManager.tempDrawColor); + this.path.strokeWidth = this.toolManager.strokeWidth; + if ((this.config['fillColor'] && this.config['fillColor'].length > 0) || this.toolManager.fillColor) { + this.path.fillColor = new Color(this.config['fillColor'] || this.toolManager.fillColor); + this.path.fillColor.alpha = this.data['fillAlpha'] || this.defaults['fillAlpha']; + } + + if (this.move_pointAdded) { + this.path.segments.pop(); + this.move_pointAdded = false; + } + + //check for complete + if (this.path.segments.length > 1) { + const distance = event.point.getDistance(this.path.segments[0].point); + if (distance < 5) { + this.cur_point = this.path.segments[0].point; + this.path.add(this.cur_point); + this.path.closed = true; + + const points = []; + for (const seg of this.path.segments) { + points.push({x: seg.point.x, y: seg.point.y}); + } + this.annotation.value = { + points, + data: { + strokeColor: this.config['drawColor'] || this.toolManager.drawColor, + fillColor: this.config['fillColor'] || this.toolManager.fillColor, + strokeWidth: this.toolManager.strokeWidth, + fillAlpha: this.data['fillAlpha'] || this.defaults['fillAlpha'] + } + }; + + this.completePolygon(points); + return; + } + } + + this.path.add(this.cur_point); + + this.drawCircle(this.cur_point); + this.toolChange$.next('Polygon'); + } + + completePolygon(points) { + if (this.fromRedo) { + this.toolManager.createClone(); + } + this.userParentGroupId = this.toolManager.userParentGroupId; + const polygon = this.coreFunctions.drawPolygon(this.annotation.value.points, this.annotation.value.data['strokeColor'], this.annotation.value.data['strokeWidth'], this.annotation.value.data['fillColor'], this.annotation.value.data['fillAlpha']); + polygon.data.annotation = this.annotation; + + const text = '(' + Math.floor(points[0]['x']) + ', ' + Math.floor(points[0]['y']) + ')'; + let label; + + if (this.toolManager.drawAnnotationLabels) { + label = this.coreFunctions.drawAnnotationText( + new Point(points[0]), + text, + this.toolManager.annotationColor, + this.toolManager.annotationTextSize + ); + } + + this.toolManager.checkingUserCreatedAnnotations(this.annotation, true, polygon, null, this.userParentGroupId, label, this.fromRedo); + + this.removePath(); + this.complete(); + this.path = null; + this.removeCircles(); + this.removePath(); + this.toolChange$.next('Polygon'); + this.toolManager.setIrisModified(true); + } + + drawCircle(point) { + const zoom = this.toolManager._iris.view.zoom; + let rad = this.point_radius / zoom; + if (rad < 1) { + rad = 1; + } else if (rad > this.point_radius) { + rad = this.point_radius; + } + + const myCircle = new Path.Circle({ + center: this.cur_point, + radius: rad + }); + myCircle.strokeColor = new Color(this.toolManager.tempDrawColor); + myCircle.fillColor = myCircle.strokeColor; + this.toolManager.tempToolLayer.addChild(myCircle); + this.circles.push(myCircle); + } + + removeCircles() { + for (const item of this.circles) { + item.remove(); + } + this.circles = []; + } + + removePath() { + if (this.path != null) { + this.path.remove(); + this.path = null; + } + } + + reAddData(annotation) { + super.reAddData(annotation); + + this.fromRedo = true; + this.annotation = annotation; + + this.completePolygon(annotation.value.points); + } + +} diff --git a/web/client/src/app/iris-display/tools/tool-components/rotate.tool.ts b/web/client/src/app/iris-display/tools/tool-components/rotate.tool.ts new file mode 100644 index 0000000..b956513 --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/rotate.tool.ts @@ -0,0 +1,97 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; +import { Annotation, AnnotationType } from '../../../types/annotation'; + +@Component({ + template: ` + + Rotation {{ -1 * rotation }} +
+
+ 180 + + -180 +
+
+ ` +}) +export class RotateTool extends IrisDrawingTool implements RequiredMembers { + name = 'Rotate'; + static icon = 'fa-redo'; + static id = 'Rotate'; + static type: IrisToolType = IrisToolType.IMAGE; + rotation = 0; + previousRotation = 0; + annotation: Annotation; + fromRedo = false; + + constructor(protected toolManager: ToolManagerService) { + super(toolManager); + } + + onToolInit() { + this.toolManager.createClone(); + this.annotation = new Annotation(RotateTool.id, AnnotationType.ROTATE, RotateTool.icon); + this.annotation.value = { + points: undefined, + data: { + adjustment: 0 + } + }; + } + + apply() { + if (this.fromRedo) { + this.toolManager.createClone(); + } + this.userParentGroupId = this.toolManager.userParentGroupId; + this.annotation.value = { + points: undefined, + data: {adjustment: this.rotation} + }; + + const diff = this.rotation - this.previousRotation; + this.coreFunctions.rotate(this.toolManager.iris, diff); + + if (!this.toolManager.annotationExists(this.annotation.id)) { + this.toolManager.checkingUserCreatedAnnotations(this.annotation, true, null, null, this.userParentGroupId, null, this.fromRedo); + this.previousRotation = 0; + } + + this.previousRotation = this.rotation; + + this.toolChange$.next('Rotate'); + this.toolManager.setIrisModified(true); + } + + resetData() { + super.resetData(); + + this.rotation = 0; + this.previousRotation = 0; + } + + reAddData(annotation) { + super.reAddData(annotation); + + this.fromRedo = true; + this.rotation = annotation.value.data['adjustment']; + this.annotation.id = annotation.id; + + this.apply(); + } +} + + diff --git a/web/client/src/app/iris-display/tools/tool-components/scale.tool.ts b/web/client/src/app/iris-display/tools/tool-components/scale.tool.ts new file mode 100644 index 0000000..ac06f00 --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/scale.tool.ts @@ -0,0 +1,139 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; +import { Size } from 'paper'; +import { Annotation, AnnotationType } from '../../../types/annotation'; + +@Component({ + template: ` + + Scale: {{ scale }} - Image Dimensions: (W: {{width}}, + H:{{height}}) +
+ + + +
+
+ `, + styles: ['div.slider-wrapper { display: flex }'] +}) +export class ScaleTool extends IrisDrawingTool implements RequiredMembers { + name = 'Scale'; + static icon = 'fa-object-ungroup'; + static id = 'Scale'; + static type: IrisToolType = IrisToolType.IMAGE; + scale = 1; + height = 1; + width = 1; + annotation: Annotation; + + _updating = false; + _originalHeight: number; + _originalWidth: number; + fromRedo = false; + + protected _layerKey = 'iris'; + + constructor(protected toolManager: ToolManagerService) { + super(toolManager); + } + + onToolInit() { + this.toolManager.createClone(); + this._originalWidth = this.toolManager._irisClone.width; + this._originalHeight = this.toolManager._irisClone.height; + this.width = this.toolManager._irisClone.width; + this.height = this.toolManager._irisClone.height; + this.annotation = new Annotation(ScaleTool.id, AnnotationType.RESIZE, ScaleTool.icon); + } + + apply() { + if (this.fromRedo) { + this.toolManager.createClone(); + } + this.userParentGroupId = this.toolManager.userParentGroupId; + if (this._updating) { + return false; + } + + this._updating = true; + + // reset iris data to original values + this.toolManager._iris.size = new Size(this._originalWidth, this._originalHeight); + this.toolManager.iris.setImageData(this.toolManager._irisClone.getImageData(null)); + // check if the new width or new height is less than 1 + if ((this._originalWidth * this.scale) < 1) { + this.width = 1; + } + if ((this._originalHeight * this.scale) < 1) { + this.height = 1; + } + + if ((this._originalHeight * this.scale) > 1 && (this._originalWidth * this.scale) > 1) { + // resize to new values + if (!this.fromRedo) { + this.width = this._originalWidth * this.scale; + this.height = this._originalHeight * this.scale; + } + } + + this.coreFunctions.resize(this.toolManager._iris, this.width, this.height); + + this.annotation.value = { + points: undefined, + data: { + width: this.width, + height: this.height + } + }; + + if (!this.toolManager.annotationExists(this.annotation.id)) { + this.toolManager.checkingUserCreatedAnnotations(this.annotation, true, null, null, this.userParentGroupId, null, this.fromRedo); + } + + // update view information + this._layer.view.update(); + + this._updating = false; + this.toolChange$.next('Scale'); + this.toolManager.setIrisModified(true); + + } + + resetData() { + super.resetData(); + + this.toolManager.createClone(); + this._originalWidth = this.toolManager._irisClone.width; + this._originalHeight = this.toolManager._irisClone.height; + + this.width = this._originalWidth; + this.height = this._originalHeight; + this.width = this._originalWidth; + + this.scale = 1; + } + + reAddData(annotation) { + super.reAddData(annotation); + + this.fromRedo = true; + this.height = annotation.value.data['height']; + this.width = annotation.value.data['width']; + this.annotation.id = annotation.id; + + this.apply(); + } +} diff --git a/web/client/src/app/iris-display/tools/tool-components/sharpen.tool.ts b/web/client/src/app/iris-display/tools/tool-components/sharpen.tool.ts new file mode 100644 index 0000000..4f92369 --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/sharpen.tool.ts @@ -0,0 +1,101 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; +import { Annotation, AnnotationType } from '../../../types/annotation'; + +@Component({ + template: ` + + Sharpen: {{ sharpen }} +
+ + + +
+
+ `, + styles: ['div.slider-wrapper { display: flex }', 'button.apply-btn { margin-top: -8px; margin-left: 8px; }'] +}) +export class SharpenTool extends IrisDrawingTool implements RequiredMembers { + name = 'Sharpen'; // name cannot be static, conflicts with Function.name + static icon = 'fa-sliders-h'; + static id = 'Sharpen'; + static type: IrisToolType = IrisToolType.IMAGE; + sharpen = 1; + + annotation: Annotation; + fromRedo = false; + + _updating = false; + + protected _layerKey = 'iris'; + + constructor(protected toolManager: ToolManagerService) { + super(toolManager); + } + + onToolInit() { + this.toolManager.createClone(); + this.annotation = new Annotation(SharpenTool.id, AnnotationType.SHARPEN, SharpenTool.icon); + this.annotation.value = { + points: undefined, + data: { + adjustment: undefined + } + }; + } + + apply() { + if (this.fromRedo) { + this.toolManager.createClone(); + } + this.userParentGroupId = this.toolManager.userParentGroupId; + if (this._updating) { +return false; +} + + this._updating = true; + this.annotation.value = { + points: undefined, + data: { + adjustment: this.sharpen + } + }; + + const imageData = this.coreFunctions.sharpen(this.toolManager._irisClone.getImageData(null), this.sharpen); + this.toolManager._iris.setImageData(imageData); + + if (!this.toolManager.annotationExists(this.annotation.id)) { + this.toolManager.checkingUserCreatedAnnotations(this.annotation, true, null, null, this.userParentGroupId, null, this.fromRedo); + } + + this._updating = false; + this.toolChange$.next('Sharpen'); + this.toolManager.setIrisModified(true); + } + + resetData() { + this.sharpen = 1; + } + + reAddData(annotation) { + super.reAddData(annotation); + + this.fromRedo = true; + this.sharpen = annotation.value.data['adjustment']; + this.annotation.id = annotation.id; + + this.apply(); + } + +} diff --git a/web/client/src/app/iris-display/tools/tool-components/unroll.tool.ts b/web/client/src/app/iris-display/tools/tool-components/unroll.tool.ts new file mode 100644 index 0000000..badc1f2 --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/unroll.tool.ts @@ -0,0 +1,253 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { ToolManagerService, UnrollStatus } from '../../tool-manager.service'; +import { Path, Raster, Point } from 'paper'; +import { FindIrisService } from '../../../find-iris/find-iris.service'; +import Color = paper.Color; + + +enum CircleState { + RESIZING, + MOVING, + REST +} + + +@Component({ + template: ` + + Move/resize the circle to cover the {{ iris.showCircle ? 'iris' : 'pupil' }} +
+ + +
+ +
+ ` +}) +export class UnrollTool extends IrisDrawingTool implements RequiredMembers { + name = 'Unroll'; + static icon = 'fa-map'; + static id = 'Unroll'; + static type: IrisToolType = IrisToolType.IMAGE; + pointRadius = 5; + requiredPointCount = 3; + innerRing: any; + outerRing: any; + points: any[] = []; + _irisRaster: InstanceType; + idSuffix: string; + + pupil: { + circle: InstanceType; + state: CircleState; + showCircle: boolean; + } = {circle: null, state: CircleState.REST, showCircle: false}; + + iris: { + circle: InstanceType; + state: CircleState; + showCircle: boolean; + } = {circle: null, state: CircleState.REST, showCircle: false}; + + data = {}; + + constructor( + protected findIrisService: FindIrisService, + protected toolManager: ToolManagerService + ) { + super(toolManager); + + this._irisRaster = this.toolManager.iris; + } + + onToolInit() { + this.idSuffix = this.toolManager.sourceName; + this.pointRadius = this.config['pointRadius'] || 5; + this.requiredPointCount = this.config['requiredPointCount'] || 3; + + this.findIrisService.initialize(this.toolManager); + const pupilCenter = new Point(this.findIrisService.pupilCenterX, this.findIrisService.pupilCenterY); + + pupilCenter.x += this.toolManager.iris.bounds.topLeft.x; + pupilCenter.y += this.toolManager.iris.bounds.topLeft.y; + + this.pupil.circle = new Path.Circle( + pupilCenter, + this.findIrisService.pupilRadius + ); + this.pupil.circle.strokeColor = new Color('lime'); + this.pupil.circle.strokeWidth = 2; + this.pupil.state = CircleState.REST; + this.pupil.circle.visible = true; + this.pupil.showCircle = true; + + const irisCenter = new Point(this.findIrisService.IrisCenterX, this.findIrisService.IrisCenterY); + irisCenter.x += this.toolManager.iris.bounds.topLeft.x; + irisCenter.y += this.toolManager.iris.bounds.topLeft.y; + + this.iris.circle = new Path.Circle( + irisCenter, + this.findIrisService.IrisRadius + ); + this.iris.circle.strokeColor = new Color('red'); + this.iris.circle.strokeWidth = 2; + this.iris.state = CircleState.REST; + this.iris.circle.visible = false; + this.iris.showCircle = false; + + this.toolManager.tempToolLayer.addChild(this.iris.circle); + this.toolManager.tempToolLayer.addChild(this.pupil.circle); + } + + onMouseDown(event) { + if (event.event.shiftKey) { + return; + } + + if (this.pupil.showCircle) { + if (this.pupil.circle.hitTest(event.point, {curves: true, tolerance: 10})) { + //the user is trying to resize the circle + this.pupil.state = CircleState.RESIZING; + } else if (this.pupil.circle.contains(event.point)) { + //the user wishes to move the circle + this.pupil.state = CircleState.MOVING; + } + } else if (this.iris.showCircle) { + if (this.iris.circle.hitTest(event.point, {curves: true, tolerance: 10})) { + //the user is trying to resize the circle + this.iris.state = CircleState.RESIZING; + } else if (this.iris.circle.contains(event.point)) { + //the user wishes to move the circle + this.iris.state = CircleState.MOVING; + } + } + } + + onMouseDrag(event) { + if (this.pupil.circle.visible == false && this.iris.circle.visible == false) { + return; + } + + if (this.pupil.showCircle) { + if (this.pupil.state == CircleState.RESIZING) { + //get the distance from the mouse point and the center + const newRadius = this.pupil.circle.bounds.center.getDistance(event.point); + const oldCenter = this.pupil.circle.bounds.center.clone(); + this.pupil.circle.bounds.width = newRadius * 2; + this.pupil.circle.bounds.height = newRadius * 2; + this.pupil.circle.bounds.center = oldCenter; + } else if (this.pupil.state == CircleState.MOVING) { + this.pupil.circle.bounds.center.x += event.delta.x; + this.pupil.circle.bounds.center.y += event.delta.y; + } + } else if (this.iris.showCircle) { + if (this.iris.state == CircleState.RESIZING) { + //get the distance from the mouse point and the center + const newRadius = this.iris.circle.bounds.center.getDistance(event.point); + const oldCenter = this.iris.circle.bounds.center.clone(); + this.iris.circle.bounds.width = newRadius * 2; + this.iris.circle.bounds.height = newRadius * 2; + this.iris.circle.bounds.center = oldCenter; + } else if (this.iris.state == CircleState.MOVING) { + this.iris.circle.bounds.center.x += event.delta.x; + this.iris.circle.bounds.center.y += event.delta.y; + } + } + } + + checkInnerOuterRings() { + // If Inner Ring is larger than outer ring, switch positions + const innerRadius = this.innerRing.radius; + const outerRadius = this.outerRing.radius; + + if (innerRadius > outerRadius) { + const tempRing = this.innerRing; + this.innerRing = this.outerRing; + this.outerRing = tempRing; + } + } + + removeRings() { + this.pupil.circle.visible = false; + this.iris.circle.visible = false; + } + + showIrisCircle() { + if (!this.iris.circle.visible) { + this.iris.circle.visible = true; + } + this.iris.showCircle = true; + this.pupil.showCircle = false; + } + + showPupilCircle() { + if (!this.pupil.circle.visible) { + this.pupil.circle.visible = true; + } + this.iris.showCircle = false; + this.pupil.showCircle = true; + } + + drawUnrollImage() { + //set the inner ring + this.innerRing = this.pupil.circle; + this.innerRing['center'] = this.pupil.circle.bounds.center; + this.innerRing['radius'] = this.pupil.circle.bounds.width / 2; + this.innerRing.name = 'inner'; + + //set the outer ring + this.outerRing = this.iris.circle; + this.outerRing['center'] = this.iris.circle.bounds.center; + this.outerRing['radius'] = this.iris.circle.bounds.width / 2; + this.outerRing.name = 'outer'; + + + if (this.innerRing.getIntersections(this.outerRing).length > 0) { + window.alert('Unroll circles may not overlap. Please move/resize the circles'); + } else { + this.checkInnerOuterRings(); + + if (this.outerRing.center.x + this.outerRing.radius > this.toolManager._iris.bounds.bottomRight.x + || this.outerRing.center.x - this.outerRing.radius < this.toolManager._iris.bounds.topLeft.x) { + window.alert('The outer ring must be contained within the image'); + } else if (this.outerRing.center.y + this.outerRing.radius > this.toolManager._iris.bounds.bottomRight.y + || this.outerRing.center.y - this.outerRing.radius < this.toolManager._iris.bounds.topLeft.y) { + window.alert('The outer ring must be contained within the image'); + } else if (this.outerRing.contains(this.innerRing.center)) { + this.toolManager.unrollStatus = UnrollStatus.INITIALIZING; + + // give the outerRing a chance to draw before performing the expensive operation of unrolling the Iris + setTimeout(() => { + this.toolManager.drawUnrolledIris(this.innerRing, this.outerRing); + this.removeRings(); + }, 100); + + // Needs to turn of the tool + this._destroy(); + + } else { + window.alert('Unroll circles may not overlap. Please move/resize the circles'); + } + }//end of else + + this.toolManager.setIrisModified(true); + } +} diff --git a/web/client/src/app/iris-display/tools/tool-components/zoom.tool.ts b/web/client/src/app/iris-display/tools/tool-components/zoom.tool.ts new file mode 100644 index 0000000..318997c --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool-components/zoom.tool.ts @@ -0,0 +1,55 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { IrisDrawingTool, RequiredMembers, IrisToolType } from '../tool.base'; +import { ToolManagerService } from '../../tool-manager.service'; + +@Component({ + template: ` + + + + + + ` +}) +export class ZoomTool extends IrisDrawingTool implements RequiredMembers { + name = 'Zoom'; + static icon = 'fa-search'; + static id = 'Zoom'; + static type: IrisToolType = IrisToolType.IMAGE; + default_zoom_factor = 1.05; + startingZoom = 1.0; // Define the starting zoom level + data = { + zoom: 0 + }; + + constructor(protected toolManager: ToolManagerService) { + super(toolManager); + } + + onZoomIn() { + this.toolManager._project.view.zoom = parseFloat((this.toolManager._project.view.zoom * this.default_zoom_factor).toFixed(2)); + this.data.zoom = this.toolManager._project.view.zoom; + } + + onZoomOut() { + this.toolManager._project.view.zoom = parseFloat((this.toolManager._project.view.zoom / this.default_zoom_factor).toFixed(2)); + this.data.zoom = this.toolManager._project.view.zoom; + } + + resetZoom() { + // Reset the zoom level to the starting zoom + this.toolManager._project.view.zoom = this.startingZoom; + this.data.zoom = this.toolManager._project.view.zoom; + } + +} \ No newline at end of file diff --git a/web/client/src/app/iris-display/tools/tool.base.ts b/web/client/src/app/iris-display/tools/tool.base.ts new file mode 100644 index 0000000..63e7da0 --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool.base.ts @@ -0,0 +1,145 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { OnInit, OnDestroy, Component } from '@angular/core'; +import { Layer, Item } from 'paper'; +import { ToolManagerService } from '../tool-manager.service'; +import { Subject } from 'rxjs'; +import { AnnotationType } from '../../types/annotation'; +import { CoreFunctions } from '../core/core-functions'; + +export enum IrisToolType { + ANNOTATION = 'ANNOTATION', + IMAGE = 'IMAGE' +} + +export class RequiredMembers { + public static icon: string; + public static id: string; + public static type: IrisToolType; +} + +@Component({ + template: '' +}) + +export class IrisDrawingTool implements OnInit, OnDestroy { + + name: string; // override to name the Tool + public static id: string; // override to match the "name" member (this.name) + public static icon: string; // override to a Font Awesome Icon (i.e: fa-user) + public static toolType: IrisToolType; // override to select which toolbar to populate + protected _layerKey: string; // override this to select a specific layer (i.e.: 'iris'). Defaults to this.name + protected data: object = {}; // override this to set default values for tool-specific variables/values + protected config: object = {}; //configuration information set in config file for this tool + public toolChange$: Subject = new Subject(); + coreFunctions: CoreFunctions = new CoreFunctions(); + modified = false; + userParentGroupId: string; + + public annotationType = AnnotationType.UNKNOWN; + protected _finalItem: InstanceType = null; + + // add properties to this object for defaults across all tools + protected defaults: object = { + drawColor: '#000', + fillColor: '#fff', + fillAlpha: 0.2 + }; + + + ////////////////////////////////////// Do not override any MEMBERS below this point //////////////////////////////////////////////// + + protected _layer: InstanceType; // The paper.Layer instance this tool references, pre-activated + public _isActive = false; // Show be no need to override this + + + ////////////////////////// Override any placeholder methods belong to define tool's behavior ////////////////////////////////////// + constructor(protected toolManager: ToolManagerService) { + } + + // Placeholder method + // this._layer will be populated by the time this fires + onToolInit() { + } + + // Placeholder method + onMouseUp(evt?) { + } + + // Placeholder method + onMouseDown(evt?) { + } + + // Placeholder method + onMouseDrag(evt?) { + } + + // Placeholder method + onMouseMove(evt?) { + } + + // Placeholder method + onMouseLeave(evt?) { + } + + reset() { + // Re-create this Component?? + } + + complete() { + this.toolManager.updateUnrollAnnotations(); + + // Dumps updates into the Layer.data field for storage + this._layer.data = Object.assign(this._layer.data, this.data); + } + + protected _destroy() { + this._isActive = false; + }; + + resetData() { + // Do nothing, as it's overridden by children + } + + reAddData(annotation) { + if (!annotation || !annotation.value) { + return; + } + } + + +////////////////////////////////////// Do not override any METHODS below this point //////////////////////////////////////////////// + //this is called when the tool on the tool bar is clicked + ngOnInit() { + // update active tool within ToolManager + this.toolManager.activeTool = this.name; + + this._layer = this.toolManager.initTool(this.name, this._layerKey); + + // Override any defaults with current values via Object.assign + this.data = Object.assign(this.data, this._layer.data); + + // Deep Clone the config data for this tool + this.config = JSON.parse(JSON.stringify(this.toolManager.contentSvc.getToolConfig(this.name))); + + // run tool-specific logic during init, this.config will exist at this point + this.onToolInit(); + + this._isActive = true; + } + + ngOnDestroy() { + // Dumps updates into the Layer.data field for storage + this._layer.data = Object.assign(this._layer.data, this.data); + + this._destroy(); + } +} diff --git a/web/client/src/app/iris-display/tools/tool.registry.ts b/web/client/src/app/iris-display/tools/tool.registry.ts new file mode 100644 index 0000000..c0a5430 --- /dev/null +++ b/web/client/src/app/iris-display/tools/tool.registry.ts @@ -0,0 +1,38 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Type, Injectable } from '@angular/core'; +import * as TOOL_COMPONENTS from './tool-components'; + +@Injectable() +export class IrisToolRegistry { + _registry: any = {}; + + constructor() { + Object.keys(TOOL_COMPONENTS).forEach(tool => { + const t = TOOL_COMPONENTS[tool]; + this._registry[t.id] = {id: t.id, name: t.id, component: t}; + }); + } + + register(name: string, component: Type) { + } + + getComponent(name) { + return this._registry[name]; + } + + get iconMap() { + return Object.keys(this._registry).reduce((map, key) => { + map[key] = this._registry[key].component.icon; + return map; + }, {}); + } +} diff --git a/web/client/src/app/irisannotation/iris-annotation-response.ts b/web/client/src/app/irisannotation/iris-annotation-response.ts new file mode 100644 index 0000000..898e459 --- /dev/null +++ b/web/client/src/app/irisannotation/iris-annotation-response.ts @@ -0,0 +1,31 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { ServiceResponse, ServiceStatus } from '../types/service-response'; +import { Polygon } from './polygon'; + +export class IrisAnnotationResponse extends ServiceResponse { + responseCode: string; + responseTime: string; + polygonList: Array; + + constructor(responseCode: string, + responseTime: string, + imageId: string, + error: string, + status: ServiceStatus, + ebtsImageId: string, + isResubmit: boolean) { + super(imageId, error, status, ebtsImageId, isResubmit); + this.responseCode = responseCode; + this.responseTime = responseTime; + this.polygonList = new Array(); + } +} diff --git a/web/client/src/app/irisannotation/iris-annotation.service.ts b/web/client/src/app/irisannotation/iris-annotation.service.ts new file mode 100644 index 0000000..9981aec --- /dev/null +++ b/web/client/src/app/irisannotation/iris-annotation.service.ts @@ -0,0 +1,115 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable, OnDestroy } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, timer, Subject, Subscription } from 'rxjs'; +import { switchMap, share, retry, takeUntil } from 'rxjs/operators'; +import { IrisAnnotationResponse } from './iris-annotation-response'; +import { ServiceStatus } from '../types/service-response'; + +@Injectable({ + providedIn: 'root' +}) +export class IrisAnnotationService implements OnDestroy { + private imageAnnotationUrl = '/api/service/annotation/'; + private imageAnnotationStatusUrl = '/api/service/annotation/status/'; + private annotationSnapshotUrl = '/api/service/annotation/snapshot/' + private annotationResponseStatus$: Observable; + public annotationResponse$ = new Subject(); + private stopAnnotationPolling$ = new Subject(); + private _annotationStatusSubscription: Subscription[] = []; + + constructor( + private http: HttpClient) { + } + + getIrisAnnotationsStatus(ebtsImageId: string, modifiedImageId: string): Observable { + return this.http.get(this.imageAnnotationStatusUrl + ebtsImageId + '/' + modifiedImageId); + } + + getIrisAnnotationsResponse(imageId: string, isResubmit: boolean): Observable { + return this.http.get(this.imageAnnotationUrl + imageId + '/' + isResubmit); + } + + startAnnotationStatusPolling(ebtsImageId: string, modifiedImageId: string): void { + this.annotationResponseStatus$ = timer(1, 5000).pipe( //5000 = 5 second polling interval + switchMap(() => + this.getIrisAnnotationsStatus(ebtsImageId, modifiedImageId) + ), + retry(4), + share(), + takeUntil(this.stopAnnotationPolling$) + ); + } + + stopAnnotationStatusPolling(imageId?: string): void { + if (imageId) { + this._annotationStatusSubscription[imageId].unsubscribe(); + } else { + this.stopAnnotationPolling$.next(true); + this.stopAnnotationPolling$.complete(); + this._annotationStatusSubscription.forEach(subscription => { + subscription.unsubscribe(); + }); + } + } + + ngOnDestroy() { + this.stopAnnotationStatusPolling(); + } + + getAnnotationSnapshot(imageData: string, ebtsImageId: string): Observable { + return this.http.post(this.annotationSnapshotUrl + ebtsImageId, imageData); + } + + requestIrisAnnotationResponse(ebtsImageId: string, isResubmit: boolean, modifiedImageId?: string): void { + let imageId = ebtsImageId; + if (modifiedImageId != ebtsImageId) { + imageId = modifiedImageId; + } else { + modifiedImageId = '-1'; + } + + this.startAnnotationStatusPolling(ebtsImageId, modifiedImageId); + const serviceStatus = ServiceStatus; + + this._annotationStatusSubscription[imageId] = this.annotationResponseStatus$.subscribe( + data => { + switch (data) { + case serviceStatus.Error: { + console.log('Iris Annotation ERROR'); + window.alert('Iris Annotation error'); + break; + } + case serviceStatus.Received: { + this.getIrisAnnotationsResponse(imageId, isResubmit).subscribe( + annotationResponse => { + if (annotationResponse.status == ServiceStatus.Received) { + annotationResponse.isResubmit = isResubmit; + this.annotationResponse$.next(annotationResponse); + this.stopAnnotationStatusPolling(imageId); + } + }); + break; + } + case serviceStatus.Pending: { + break; + } + case serviceStatus.NotSent: { + break; + } + default: + console.log('Iris Annotation: No Status Yet'); + break; + } + }); + } +} diff --git a/web/client/src/app/irisannotation/polygon.ts b/web/client/src/app/irisannotation/polygon.ts new file mode 100644 index 0000000..2261733 --- /dev/null +++ b/web/client/src/app/irisannotation/polygon.ts @@ -0,0 +1,29 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { IwpPoint } from '../tshepii/iwp-point'; + +export class Polygon { + name: string; + code: number; + color: string; + outline: Array; + + constructor( + name: string, + code: number, + color: string + ) { + this.name = name; + this.code = code; + this.color = color; + this.outline = new Array(); + } +} diff --git a/web/client/src/app/manage/manage.component.css b/web/client/src/app/manage/manage.component.css new file mode 100644 index 0000000..3f69d62 --- /dev/null +++ b/web/client/src/app/manage/manage.component.css @@ -0,0 +1,21 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +.person-dialog-content { + min-width: 500px; +} + +.confirm-cancel-btn-group { + float: right; +} + +.mat-mdc-header-cell { + font-weight: bold !important; +} diff --git a/web/client/src/app/manage/manage.component.html b/web/client/src/app/manage/manage.component.html new file mode 100644 index 0000000..79ddb3b --- /dev/null +++ b/web/client/src/app/manage/manage.component.html @@ -0,0 +1,142 @@ + +
+
+
+

Manage Requesters

+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name{{person.name}}Title{{person.title}}Department{{person.department}}Address{{person.address}}Email{{person.email}}Phone{{person.phone}}Modify + + +
+ +
+
+
+ +
+ +
+ + + +

{{ editing ? "Update" : "Create" }} Requester

+
+
+
+ + +
Entry must be 2-40 alpha characters. +
+
+
+ + +
The email is invalid. Example email: email@example.com +
+
+
+ + +
The entry is invalid. Use the format: xxx-xxx-xxxx +
+
+
+ + +
Entry must be less than 40 characters. +
+
+
+ + +
Entry must be less than 40 characters. +
+
+
+ + +
Entry must be less than 100 characters. +
+
+
+
+
+ + +
+
+ + + +

Remove Requester

+
+ Are you sure you'd like to remove this requester? +
+
+ + +
+
diff --git a/web/client/src/app/manage/manage.component.spec.ts b/web/client/src/app/manage/manage.component.spec.ts new file mode 100644 index 0000000..1618189 --- /dev/null +++ b/web/client/src/app/manage/manage.component.spec.ts @@ -0,0 +1,61 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ManageComponent } from './manage.component'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatTableModule } from '@angular/material/table'; +import { Person } from '../types/person'; +import { FormsModule } from '@angular/forms'; + +describe('XVII. The ManageComponent class:', () => { + let component: ManageComponent; + let fixture: ComponentFixture; + const pers = new Person(); + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [FormsModule, HttpClientTestingModule, MatDialogModule, MatSnackBarModule, MatTableModule], + declarations: [ManageComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ManageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('a. getAllPersons takes zero parameters and returns a void type.', () => { + expect(component.getAllPersons()).toBeUndefined(); + }); + + it('b. createNewPerson takes zero parameters and returns a void type.', () => { + expect(component.createNewPerson()).toBeUndefined(); + }); + + it('c. closePerson takes zero parameters and returns a void type.', () => { + expect(component.closePerson()).toBeUndefined(); + }); + + it('d. savePerson takes zero parameters and returns a void type.', () => { + expect(component.savePerson()).toBeUndefined(); + }); + + it('e. removePerson takes an object instance of the Person class as a parameter and returns a void type.', () => { + expect(component.removePerson(pers)).toBeUndefined(); + }); + +}); diff --git a/web/client/src/app/manage/manage.component.ts b/web/client/src/app/manage/manage.component.ts new file mode 100644 index 0000000..3b5eb7c --- /dev/null +++ b/web/client/src/app/manage/manage.component.ts @@ -0,0 +1,153 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { + Component, + OnInit, + ElementRef, + TemplateRef + } from '@angular/core'; +import { PersonService } from '../person/person.service'; +import { Person } from '../types/person'; +import { ViewChild } from '@angular/core'; +import { NgForm } from '@angular/forms'; +import { MatTableDataSource } from '@angular/material/table'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-manage', + templateUrl: './manage.component.html', + styleUrls: ['./manage.component.css'] +}) +export class ManageComponent implements OnInit { + + persons: Person[] = []; + personsData: MatTableDataSource; + personsColumns = ['name', 'title', 'department', 'address', 'email', 'phone', 'actions']; + @ViewChild(MatPaginator, {static: true}) paginator: MatPaginator; + + currentPerson: Person = this.personService.createEmptyPerson(); + createPerson: Person = this.personService.createEmptyPerson(); + errorMessage: string; + editing = true; + + p = 1; + + @ViewChild('addPersonModal', {static: true}) + addPersonModal: ElementRef; + + @ViewChild('removeConfirmModal', {static: true}) + removeConfirmModal: ElementRef; + + @ViewChild('createForm', {static: true}) + createForm: NgForm; + + @ViewChild('currentPersonSelect', {static: true}) + currentProfileSelect: ElementRef; + + constructor( + private personService: PersonService, + private matDialogService: MatDialog, + private matSnackBar: MatSnackBar + ) { + } + + ngOnInit() { + this.getAllPersons(); + } + + applyFilter($event: KeyboardEvent): void { + var value = ($event.target as HTMLTextAreaElement).value; + this.personsData.filter = value.trim().toLowerCase(); + } + + getAllPersons() { + //pull down the profiles and reset the current profile + this.personService.getPersons() + .subscribe(prfs => { + this.persons = prfs; + for (const person of this.persons) { + if (person.name == this.currentPerson.name) { + this.currentPerson = person; + } + } + this.personsData = new MatTableDataSource(prfs); + }); + } + + resetModalInputs() { + this.createPerson = this.personService.createEmptyPerson(); + this.createForm.resetForm(); + } + + createNewPerson() { + const createdPerson = this.createPerson; + + this.personService.addPerson(this.createPerson) + .subscribe( + response => { + + //set the current profile to the one just created + this.currentPerson = createdPerson; + + //reset the create modal + this.resetModalInputs(); + + this.getAllPersons(); + }, + response => { + this.errorMessage = JSON.parse(response.error).message; + console.error(this.errorMessage); + }); + } + + closePerson() { + console.log('Close Person'); + this.getAllPersons(); + this.matDialogService.closeAll(); + } + + savePerson() { + this.personService.addPerson(this.currentPerson) + .subscribe(response => { + this.getAllPersons(); + this.matDialogService.closeAll(); + this.matSnackBar.open('Requester saved', 'Dismiss'); + }); + } + + removePerson(person: Person) { + this.personService.removePerson(person).subscribe(response => { + this.currentPerson = this.personService.createEmptyPerson(); + this.getAllPersons(); + this.matDialogService.closeAll(); + this.matSnackBar.open('Requester removed', 'Dismiss'); + }); + } + + removePersonClicked(person: Person, modal: TemplateRef) { + this.currentPerson = person; + this.matDialogService.open(modal); + } + + personEditClicked(person: Person, modal: TemplateRef) { + this.editing = true; + this.currentPerson = JSON.parse(JSON.stringify(person)); + this.matDialogService.open(modal); + } + + newPersonClicked(modal: TemplateRef) { + this.editing = false; + this.currentPerson = this.personService.createEmptyPerson(); + this.matDialogService.open(modal); + } +} diff --git a/web/client/src/app/manage/person-filter.pipe.spec.ts b/web/client/src/app/manage/person-filter.pipe.spec.ts new file mode 100644 index 0000000..a789841 --- /dev/null +++ b/web/client/src/app/manage/person-filter.pipe.spec.ts @@ -0,0 +1,39 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { TestBed, inject } from '@angular/core/testing'; +import { PersonFilterPipe } from './person-filter.pipe'; +import { Person } from '../types/person'; + +describe('XVIII. The PersonFilterPipe component class: ', () => { + let pers1: Person = new Person(); + pers1 = {id: '12345', name:'John', title:'Archduke', department:'Sporting Goods', address: '123 Street St, A City, A State', email:'john@iwp.org', phone:'555-5555'}; + + let pers2: Person = new Person(); + pers2 = {id: '12345', name:'Jane', title:'King', department:'Housewares', address: '123 Street St, A City, A State', email:'jane@iwp.org', phone:'555-5556'}; + + const args = 'John'; + const DEBUG = false; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [PersonFilterPipe ] + }); + }); + + it(`a. transform takes an Array of the Person class like ${[JSON.stringify(pers1), JSON.stringify(pers2)]} and any args (i.e., search terms) like '${args}' provided as parameters, and returns the matched person (please see console statement).`, inject([PersonFilterPipe], (service: PersonFilterPipe) => { + const eighteenA = service.transform([pers1,pers2], args); + if (DEBUG) { + console.log('UNIT TEST XVIII(a): transform returns', eighteenA); + } + + expect(eighteenA).toBeInstanceOf(Array); + })); +}); diff --git a/web/client/src/app/manage/person-filter.pipe.ts b/web/client/src/app/manage/person-filter.pipe.ts new file mode 100644 index 0000000..0033e1c --- /dev/null +++ b/web/client/src/app/manage/person-filter.pipe.ts @@ -0,0 +1,40 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Pipe, PipeTransform } from '@angular/core'; +import { Person } from '../types/person'; + +@Pipe({ + name: 'personFilter' +}) +export class PersonFilterPipe implements PipeTransform { + + transform(persons: Person[], args?: any): any { + // Sort List + const sortedPersons = persons.sort((p1, p2) => { + const name1 = p1.name.toLowerCase(); + const name2 = p2.name.toLowerCase(); + if (name1 > name2) { + return 1; + } + if (name1 < name2) { + return -1; + } + return 0; + }); + + if (args == '') { +return sortedPersons; +} + + return sortedPersons.filter(person => person.name.toLowerCase().indexOf(args.toLowerCase()) !== -1); + } + +} diff --git a/web/client/src/app/notifications/notifications.component.css b/web/client/src/app/notifications/notifications.component.css new file mode 100644 index 0000000..e80647e --- /dev/null +++ b/web/client/src/app/notifications/notifications.component.css @@ -0,0 +1,27 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +.menu-footer { + background-color: lightgray; + padding-bottom: 6px; +} + +.notification-item { + height: unset !important; +} + +.notification-item .mat-line { + white-space: pre-wrap !important; +} + +.mat-success { + background-color: green; + color: #fff; +} diff --git a/web/client/src/app/notifications/notifications.component.html b/web/client/src/app/notifications/notifications.component.html new file mode 100644 index 0000000..a9266da --- /dev/null +++ b/web/client/src/app/notifications/notifications.component.html @@ -0,0 +1,47 @@ + + + +
+ No new notifications, congratulations! +
+ + + + + {{notification.icon}} +
{{notification.message}}
+ +
+
+ + +
diff --git a/web/client/src/app/notifications/notifications.component.ts b/web/client/src/app/notifications/notifications.component.ts new file mode 100644 index 0000000..0319887 --- /dev/null +++ b/web/client/src/app/notifications/notifications.component.ts @@ -0,0 +1,121 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component, OnInit, AfterViewInit } from '@angular/core'; +import { AppNotification } from '../types/notification'; +import { NotificationsService } from './notifications.service'; +import { Router } from '@angular/router'; +import { ProfileService } from '../profile/profile.service'; + +@Component({ + selector: 'app-notifications', + templateUrl: './notifications.component.html', + styleUrls: ['./notifications.component.css'] +}) +export class NotificationsComponent implements OnInit { + selected: any = undefined; + notifications: AppNotification[]; + messageIndexForTesting = 0; + currentUserId = 'null'; + + constructor(public noteService: NotificationsService, + private router: Router, + private profileService: ProfileService, + ) { + } + + ngOnInit() { + this.getCurrentUser(); + this.getNotificationServiceArray(); + } + + getCurrentUser(): void { + this.profileService.currentProfile$.subscribe((profile) => { + this.currentUserId = profile.id; + this.getAllNotificationsForUser(); + }); + } + + getNotificationServiceArray(): void { + this.notifications = this.noteService.notificationsArray; + } + + getAllNotificationsForUser(): void { + this.noteService.getAllNotificationsForUser(this.currentUserId).subscribe( + notificationResponse => { + notificationResponse.forEach(note => { + if (!this.notifications.find(currNote => currNote.id == note.id)){ + this.noteService.addNewNotification(note, true); + } + }); + }); + this.noteService.pollForNewNotifications(this.currentUserId); + } + + get newNotificationCount(): number { + this.getNotificationServiceArray(); + return this.notifications.filter(note => note.isNew).length; + } + + clickNotification(note: AppNotification): void { + note.isNew = false; + this.resetButtonToggle(); + if (note.action.includes('download')) { + window.open(note.action); + } else { + this.router.navigateByUrl(note.action).then(res => { + }); + } + + this.noteService.markSeen(note) + .subscribe( + noteSeen => { + }); + } + + clearNotifications(): void { + this.noteService.markAllSeen(); + } + + deleteNotification(noteToDelete: AppNotification): void { + this.resetButtonToggle(); + this.noteService.deleteNote(noteToDelete).subscribe( + noteDeleted => { + this.getNotificationServiceArray(); + }); + } + + resetButtonToggle(): void { + this.selected = null; + } + + deleteAllNotifications(): void { + this.noteService.deleteAllNotes(); + } + + addNotification() { + this.postNotification({ + message: this.messageIndexForTesting.toString(), + action: '/', + dateCreated: new Date(), + icon: 'notifications', + isNew: true, + id: '999', + userId: 1 + }); + this.messageIndexForTesting += 1; + } + + postNotification(noteToAdd: AppNotification) { + this.noteService.postNotification(noteToAdd).subscribe(data => { + noteToAdd.id = data.toString(); + }); + } +} diff --git a/web/client/src/app/notifications/notifications.service.spec.ts b/web/client/src/app/notifications/notifications.service.spec.ts new file mode 100644 index 0000000..327427e --- /dev/null +++ b/web/client/src/app/notifications/notifications.service.spec.ts @@ -0,0 +1,85 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { TestBed, inject } from '@angular/core/testing'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { NotificationsService } from './notifications.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { AppNotification } from '../types/notification'; +import { Observable } from 'rxjs'; + +describe('XIX. The NotificationsService class:', () => { + const userId = '12345'; + const message = 'Hello World'; + const icon = 'myIcon'; + const action = 'modify'; + const numId = 12345; + const appNote = new AppNotification(message, icon, action, numId); + + beforeEach(() => TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, MatSnackBarModule, RouterTestingModule] + }) + .compileComponents() + ); + + it(`a. pollForNewNotifications takes a string like '${userId}' as a parameter and returns a void type.`, inject([NotificationsService], (service: NotificationsService) => { + spyOn(service, 'pollForNewNotifications'); + const res = service.pollForNewNotifications(userId); + expect(res).toBeUndefined(); + })); + + it(`b. checkIfNoteWasNotified takes an object instance of the AppNotification class like '${JSON.stringify(appNote)}' as a parameter and returns a void type.`, inject([NotificationsService], (service: NotificationsService) => { + spyOn(service, 'checkIfNoteWasNotified'); + const res = service.checkIfNoteWasNotified(appNote); + expect(res).toBeUndefined(); + })); + + it(`c. postNotification takes an object instance of the AppNotification class like '${JSON.stringify(appNote)}' as a parameter and returns an Observable AppNotification.`, inject([NotificationsService], (service: NotificationsService) => { + const res = service.postNotification(appNote); + expect(res).toBeInstanceOf(Observable); + + })); + + it(`d. addNewNotification takes an object instance of the AppNotification class like '${JSON.stringify(appNote)}' and a boolean value as parameters and returns a void type.`, inject([NotificationsService], (service: NotificationsService) => { + const res = service.addNewNotification(appNote, true); + expect(res).toBeUndefined(); + })); + + it(`e. getAllNotificationsForUser takes a string like '${userId}' as a parameter and returns an Observable array of AppNotifications.`, inject([NotificationsService], (service: NotificationsService) => { + const res = service.getAllNotificationsForUser(userId); + expect(res).toBeInstanceOf(Observable); + })); + + it(`f. getNewUserNotificationsForUser takes a string like '${userId}' as a parameter and returns an Observable array of AppNotifications.`, inject([NotificationsService], (service: NotificationsService) => { + const res = service.getNewUserNotificationsForUser(userId); + expect(res).toBeInstanceOf(Observable); + })); + + it(`g. markSeen takes an object instance of the AppNotification class like '${JSON.stringify(appNote)}' as a parameter and returns an Observable string.`, inject([NotificationsService], (service: NotificationsService) => { + const res = service.markSeen(appNote); + expect(res).toBeInstanceOf(Observable); + })); + + it(`h. markAllSeen takes zero parameters and returns a void type.`, inject([NotificationsService], (service: NotificationsService) => { + const res = service.markAllSeen(); + expect(res).toBeUndefined(); + })); + + it(`i. deleteNote takes an object instance of the AppNotification class like '${JSON.stringify(appNote)}' as a parameter and returns an Observable string.`, inject([NotificationsService], (service: NotificationsService) => { + const res = service.deleteNote(appNote); + expect(res).toBeInstanceOf(Observable); + })); + + it(`j. deleteAllNotes takes zero parameters and returns a void type.`, inject([NotificationsService], (service: NotificationsService) => { + const res = service.deleteAllNotes(); + expect(res).toBeUndefined(); + })); +}); diff --git a/web/client/src/app/notifications/notifications.service.ts b/web/client/src/app/notifications/notifications.service.ts new file mode 100644 index 0000000..484b3bc --- /dev/null +++ b/web/client/src/app/notifications/notifications.service.ts @@ -0,0 +1,139 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { AppNotification } from '../types/notification'; +import { Injectable } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { Observable, throwError as observableThrowError } from 'rxjs'; +import { startWith, switchMap, catchError } from 'rxjs/operators'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { interval } from 'rxjs/internal/observable/interval'; + +// https://stackoverflow.com/questions/36395252/how-to-get-data-from-observable-in-angular2 +@Injectable({ + providedIn: 'root' +}) + +export class NotificationsService { + notificationsArray: AppNotification[]; + notifiedIdSet = new Set(); + private addNoteUrl = '/api/notification/add'; + private getAllNoteUrl = '/api/notification/all/'; + private getNewNoteUrl = '/api/notification/new/'; + private markNoteReadUrl = '/api/notification/markRead/'; + private deleteNoteUrl = '/api/notification/delete/'; + private notificationPollingInterval = 15000; // (5000 = 5s) + + constructor( + private matSnackBar: MatSnackBar, + private http: HttpClient + ) { + this.initializeUserNotifications(); + } + + // Only to be used for initial pull of user notifications upon service load, intentionally does /not/ trigger matSnackBar + private initializeUserNotifications(): void { + this.notificationsArray = []; + this.notifiedIdSet.clear(); + } + + pollForNewNotifications(userId: string): void { + interval(this.notificationPollingInterval) + .pipe( + startWith(0), + switchMap(() => this.getNewUserNotificationsForUser(userId)) + ).subscribe(newNotes => { + newNotes.forEach(note => { + this.checkIfNoteWasNotified(note); + }); + }); + } + + checkIfNoteWasNotified(noteToCheck: AppNotification) { + if (this.notifiedIdSet.has(noteToCheck.id)) { + //do nothing + } else { + this.addNewNotification(noteToCheck, false); + } + } + + postNotification(notifcationToAdd: AppNotification): Observable { + const headers = new HttpHeaders().append('Content-Type', 'application/json'); + return this.http.post(this.addNoteUrl, notifcationToAdd, + {headers}) + .pipe(catchError(this.handleError)); + } + + addNewNotification(noteToAdd: AppNotification, isInitilization: boolean): void { + // Add notification to the current array + this.notificationsArray.unshift(noteToAdd); + this.notifiedIdSet.add(noteToAdd.id); + // Trigger popup + if (!isInitilization) { + const snackbarRef = this.matSnackBar.open(noteToAdd.message, 'Go'); + // Bind popup button to action + snackbarRef.onAction().subscribe(() => { + noteToAdd.isNew = false; + window.open(noteToAdd.action); + }); + } + } + + getAllNotificationsForUser(userId: string): Observable { + return this.http.get(this.getAllNoteUrl + userId) + .pipe(catchError(this.handleError)); + } + + getNewUserNotificationsForUser(userId: string): Observable { + return this.http.get(this.getNewNoteUrl + userId) + .pipe(catchError(this.handleError)); + } + + // Mark a single notification as 'seen', updates server accordingly + // hit server endpoint to ensure the isNew flag gets updated centrally + markSeen(note: AppNotification): Observable { + note.isNew = false; + return this.http.get(this.markNoteReadUrl + note.id, {responseType: 'text'}) + .pipe(catchError(this.handleError)); + } + + + // Marks all notifications for this user as 'seen', updates server accordingly + markAllSeen(): void { + this.notificationsArray.forEach(note => note.isNew = false); + // hit server endpoint to update isNew flag for all of this user's notifications + this.notificationsArray.forEach( + note => this.markSeen(note).subscribe( + noteSeen => { + }) + ); + } + + deleteNote(noteToDelete: AppNotification): Observable { + noteToDelete.isNew = false; + this.notificationsArray = this.notificationsArray.filter(note => note.id !== noteToDelete.id); + return this.http.get(this.deleteNoteUrl + noteToDelete.id, {responseType: 'text'}).pipe(catchError(this.handleError)); + } + + deleteAllNotes(): void { + this.notificationsArray.forEach( + note => this.deleteNote(note).subscribe( + noteToDelete => { + }) + ); + } + + private handleError(error: any) { + const errMsg = (error.message) ? error.message : error.status ? `${error.status} - ${error.statusText}` : 'Server error'; + console.log(error); + return observableThrowError(errMsg); + } + +} diff --git a/web/client/src/app/page-not-found/page-not-found.component.css b/web/client/src/app/page-not-found/page-not-found.component.css new file mode 100644 index 0000000..7081599 --- /dev/null +++ b/web/client/src/app/page-not-found/page-not-found.component.css @@ -0,0 +1,9 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ \ No newline at end of file diff --git a/web/client/src/app/page-not-found/page-not-found.component.html b/web/client/src/app/page-not-found/page-not-found.component.html new file mode 100644 index 0000000..0687796 --- /dev/null +++ b/web/client/src/app/page-not-found/page-not-found.component.html @@ -0,0 +1,13 @@ + +
+

404 Error

+

Page Not Found

+
diff --git a/web/client/src/app/page-not-found/page-not-found.component.spec.ts b/web/client/src/app/page-not-found/page-not-found.component.spec.ts new file mode 100644 index 0000000..29a0043 --- /dev/null +++ b/web/client/src/app/page-not-found/page-not-found.component.spec.ts @@ -0,0 +1,33 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageNotFoundComponent } from './page-not-found.component'; + +describe('PageNotFoundComponent', () => { + let component: PageNotFoundComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageNotFoundComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PageNotFoundComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/web/client/src/app/page-not-found/page-not-found.component.ts b/web/client/src/app/page-not-found/page-not-found.component.ts new file mode 100644 index 0000000..8b0b826 --- /dev/null +++ b/web/client/src/app/page-not-found/page-not-found.component.ts @@ -0,0 +1,20 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-page-not-found', + templateUrl: './page-not-found.component.html', + styleUrls: ['./page-not-found.component.css'] +}) +export class PageNotFoundComponent { + +} diff --git a/web/client/src/app/pdm-confirmation-dialog/pdm-confirmation-dialog.component.css b/web/client/src/app/pdm-confirmation-dialog/pdm-confirmation-dialog.component.css new file mode 100644 index 0000000..40b54cb --- /dev/null +++ b/web/client/src/app/pdm-confirmation-dialog/pdm-confirmation-dialog.component.css @@ -0,0 +1,45 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +.cancel-button { + background-color: #707070; + color: white; +} + +.bullet-points { + background-color: #89898972; + padding-left: 3px; + padding-right: 4px; +} + +.confirm-button { + background-color: #3f51b5; + color: #fff; +} + +.back-button { + background-color: #ff4081; + color: #fff; +} + +.error-label { + font-size: 18px; + font-weight: bold; + color: #333; +} + +.pdm-confirmation { + padding: 20px; +} + +.warning-message { + color: #a94442; + padding: 3px; +} \ No newline at end of file diff --git a/web/client/src/app/pdm-confirmation-dialog/pdm-confirmation-dialog.component.html b/web/client/src/app/pdm-confirmation-dialog/pdm-confirmation-dialog.component.html new file mode 100644 index 0000000..0b4c603 --- /dev/null +++ b/web/client/src/app/pdm-confirmation-dialog/pdm-confirmation-dialog.component.html @@ -0,0 +1,36 @@ + + +
+ + + +
\ No newline at end of file diff --git a/web/client/src/app/pdm-confirmation-dialog/pdm-confirmation-dialog.component.ts b/web/client/src/app/pdm-confirmation-dialog/pdm-confirmation-dialog.component.ts new file mode 100644 index 0000000..2301866 --- /dev/null +++ b/web/client/src/app/pdm-confirmation-dialog/pdm-confirmation-dialog.component.ts @@ -0,0 +1,26 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component } from '@angular/core'; +import { MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'app-pdm-confirmation-dialog', + templateUrl: './pdm-confirmation-dialog.component.html', + styleUrl: './pdm-confirmation-dialog.component.css' +}) +export class PdmConfirmationDialogComponent { + constructor(private dialogRef: MatDialogRef) {} + + onOptionSelected(selectedChoice: string) { + this.dialogRef.close(selectedChoice); + } + +} diff --git a/web/client/src/app/pdm-confirmation-dialog/pdm-confirmation-dialog.module.ts b/web/client/src/app/pdm-confirmation-dialog/pdm-confirmation-dialog.module.ts new file mode 100644 index 0000000..11c2a83 --- /dev/null +++ b/web/client/src/app/pdm-confirmation-dialog/pdm-confirmation-dialog.module.ts @@ -0,0 +1,29 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +// Import any shared components, directives, or pipes here +import { PdmConfirmationDialogComponent } from './pdm-confirmation-dialog.component'; + +@NgModule({ + declarations: [ + PdmConfirmationDialogComponent, // Add shared components here + ], + imports: [ + CommonModule // Always import CommonModule in feature modules + ], + exports: [ + PdmConfirmationDialogComponent, // Export so other modules can use it + CommonModule // Optionally export CommonModule for convenience + ] +}) +export class PdmConfirmationDialogModule { } \ No newline at end of file diff --git a/web/client/src/app/person/person.service.spec.ts b/web/client/src/app/person/person.service.spec.ts new file mode 100644 index 0000000..479855d --- /dev/null +++ b/web/client/src/app/person/person.service.spec.ts @@ -0,0 +1,46 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { TestBed, inject } from '@angular/core/testing'; +import { Observable } from 'rxjs'; +import { Person } from '../types/person'; +import { PersonService } from './person.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; + +describe('XX. The PersonService class: ', () => { + const pers = new Person(); + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [PersonService] + }); + }); + + it('a. getPersons takes zero parameters and returns an Obsevable Array of Persons.', inject([PersonService], (service: PersonService) => { + const res = service.getPersons(); + expect(res).toBeInstanceOf(Observable); + })); + + it(`b. addPerson takes an object instance of the Person class like ${JSON.stringify(pers)} and returns an Obsevable string.`, inject([PersonService], (service: PersonService) => { + const res = service.addPerson(pers); + expect(res).toBeInstanceOf(Observable); + })); + + it(`c. removePerson takes an object instance of the Person class like ${JSON.stringify(pers)} and returns an Obsevable string.`, inject([PersonService], (service: PersonService) => { + const res = service.removePerson(pers); + expect(res).toBeInstanceOf(Observable); + })); + + it(`d. createEmptyPerson takes zero parameters and returns an empty object.`, inject([PersonService], (service: PersonService) => { + const res = service.createEmptyPerson(); + expect(res).toBeInstanceOf(Object); + })); +}); diff --git a/web/client/src/app/person/person.service.ts b/web/client/src/app/person/person.service.ts new file mode 100644 index 0000000..c0bd208 --- /dev/null +++ b/web/client/src/app/person/person.service.ts @@ -0,0 +1,60 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; +import { Person } from '../types/person'; + +import { Observable } from 'rxjs'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; + +@Injectable( + {providedIn: 'root'} +) +export class PersonService { + + getPersonsUrl = '/api/caseinfo/persons'; + addPersonUrl = '/api/caseinfo'; + removePersonUrl = '/api/caseinfo/delete/person'; + + constructor( + private http: HttpClient + ) { + } + + getPersons(): Observable { + return this.http.get(this.getPersonsUrl); + } + + addPerson(newPerson: Person): Observable { + return this.http.post(this.addPersonUrl, newPerson, { + headers: new HttpHeaders({'Content-Type': 'application/json'}), + responseType: <'text'> 'text' + }); + } + + removePerson(person: Person): Observable { + return this.http.post(this.removePersonUrl, person, { + headers: new HttpHeaders({'Content-Type': 'application/json'}), + responseType: <'text'> 'text' + }); + } + + createEmptyPerson() { + return { + id: '', + name: '', + title: '', + department: '', + address: '', + email: '', + phone: '' + }; + } +} diff --git a/web/client/src/app/profile/profile.service.spec.ts b/web/client/src/app/profile/profile.service.spec.ts new file mode 100644 index 0000000..e41904f --- /dev/null +++ b/web/client/src/app/profile/profile.service.spec.ts @@ -0,0 +1,62 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { TestBed, inject } from '@angular/core/testing'; +import { Observable } from 'rxjs'; +import { Profile } from './profile'; +import { ProfileService } from './profile.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; + +describe('XXI. The ProfileService class:', () => { + const profile: Profile = new Profile(); + + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ProfileService] + }); + }); + + it('a. getProfile takes zero parameters and returns an Observable Profile.', inject([ProfileService], (service: ProfileService) => { + const res = service.getProfile(); + expect(res).toBeInstanceOf(Observable); + })); + + it('b. updateProfile takes an object instance of the Profile class as a parameter and returns a void type.', inject([ProfileService], (service: ProfileService) => { + const res = service.updateProfile(profile); + expect(res).toBeUndefined(); + })); + + it('c. createEmptyProfile takes an zero parameters and returns an empty object instance of the Profile class.', inject([ProfileService], (service: ProfileService) => { + const res = service.createEmptyProfile(); + expect(res).toBeInstanceOf(Object); + })); + + it('d. getCurrentProfile takes zero parameters and returns a void type.', inject([ProfileService], (service: ProfileService) => { + const res = service.getCurrentProfile(); + expect(res).toBeInstanceOf(Object); + })); + + it('e. setCurrentProfile takes an object instance of the Profile class as a parameter and returns a void type.', inject([ProfileService], (service: ProfileService) => { + const res = service.setCurrentProfile(profile); + expect(res).toBeUndefined(); + })); + + it('f. getUserPrivileges takes zero parameters and returns an Obervable Object.', inject([ProfileService], (service: ProfileService) => { + const res = service.getUserPrivileges(); + expect(res).toBeInstanceOf(Object); + })); + + it('g. getUserList takes zero parameters and returns and Observable Object.', inject( [ProfileService], (service: ProfileService) => { + const res = service.getUserList(); + expect(res).toBeInstanceOf(Object); + })); +}); diff --git a/web/client/src/app/profile/profile.service.ts b/web/client/src/app/profile/profile.service.ts new file mode 100644 index 0000000..e58a8d0 --- /dev/null +++ b/web/client/src/app/profile/profile.service.ts @@ -0,0 +1,116 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; +import { Profile } from './profile'; + +import { Observable, Subject } from 'rxjs'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { map } from 'rxjs/operators'; + +@Injectable( + {providedIn: 'root'} +) +export class ProfileService { + + public currentProfile$: Subject = new Subject(); + public username$: Subject = new Subject(); + + baseUrl = '/api/admin/'; + userUrl = '/api/admin/user'; + profilesUrl = '/api/admin/user/profile'; + private privUrl = '/api/admin/user/privileges'; + private usersUrl = '/api/admin/userList'; + + + private currentProfile: Profile = this.createEmptyProfile(); + private _username: string; + private _privileges: object; + + constructor(private http: HttpClient) { + + this.http.get(this.userUrl, {responseType: <'text'> 'text'}).subscribe(response => { + this._username = response; + this.username$.next(this._username); + }); + + this.getUserPrivileges().subscribe(privs => { + this._privileges = privs; + }); + + this.getProfile().subscribe((profile) => { + if (!profile.userSetting) { + const prof = this.createEmptyProfile(); + profile.userSetting = prof.userSetting; + } + this.setCurrentProfile(profile); + }); + } + + get username() { + return this._username; + } + + set username(s: string) { + } + + get currentPrivileges() { + return this._privileges; + } + + getProfile(): Observable { + return this.http.get(this.profilesUrl); + } + + updateProfile(updatedProfile: Profile) { + this.http.post(this.profilesUrl, updatedProfile, { + headers: new HttpHeaders({'Content-Type': 'application/json'}), + responseType: <'text'> 'text', + observe: 'response' + }) + .subscribe(response => { + this.getProfile() + .subscribe((profile) => { + this.setCurrentProfile(profile); + }); + }); + } + + createEmptyProfile() { + return new Profile(); + } + + getCurrentProfile() { + return Object.assign({}, this.currentProfile); + } + + setCurrentProfile(profile: Profile) { + this.currentProfile = profile; + this.currentProfile$.next(profile); + } + + getUserPrivileges(): Observable { + return this.http.get(this.privUrl + '') + .pipe(map((response) => { + //convert to object + const obj = {}; + Object.keys(response).forEach(idx => { + obj[response[idx]] = response[idx]; + }); + + return obj; + })); + } + + getUserList(): Observable { + return this.http.get(this.usersUrl); + } + +} diff --git a/web/client/src/app/profile/profile.ts b/web/client/src/app/profile/profile.ts new file mode 100644 index 0000000..019723f --- /dev/null +++ b/web/client/src/app/profile/profile.ts @@ -0,0 +1,62 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +export class Profile { + id: string; + name: string; + destinationAgencyIdentifier: string; + originatingAgencyIdentifier: string; + attentionIndicator: string; + sourceAgency: string; + isReportExclude: boolean; + + fullName: string; + email: string; + phoneNumber: string; + title: string; + department: string; + address: string; + userSetting: UserSetting; + permissions: any; + + constructor() { + this.id= ''; + this.name= ''; + this.destinationAgencyIdentifier= ''; + this.originatingAgencyIdentifier= ''; + this.attentionIndicator= ''; + this.sourceAgency= ''; + this.isReportExclude= false; + this.fullName= ''; + this.email= ''; + this.phoneNumber= ''; + this.title= ''; + this.department= ''; + this.address= ''; + this.permissions= {}; + this.userSetting = new UserSetting(); + } +}//end of Profile + +export class UserSetting { + strokeWidth: number; + drawColor: string; + fillColor: string; + numHistory: number; + tshepiiStrokeOpacity: number; + + constructor() { + this.strokeWidth = 2; + this.drawColor = '#127bdc'; + this.fillColor = '#333333'; + this.numHistory = 10; + this.tshepiiStrokeOpacity = 0; + } +} \ No newline at end of file diff --git a/web/client/src/app/pupil-dilation-tool/pupil-dilation-response.ts b/web/client/src/app/pupil-dilation-tool/pupil-dilation-response.ts new file mode 100644 index 0000000..607738a --- /dev/null +++ b/web/client/src/app/pupil-dilation-tool/pupil-dilation-response.ts @@ -0,0 +1,85 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { ServiceResponse, ServiceStatus } from '../types/service-response'; + + +export class PupilDilationResponse extends ServiceResponse { + responseStatus: ServiceStatus; + alphaBytes: DeformedImageBytes; + alphaImageData: DeformedImageData; + + constructor( + imageId: string, + error: string, + modeStatus: ServiceStatus, + responseStatus: ServiceStatus, + ebtsImageId: string, + // alphaScoreMapper connects alphaScore (string) to a DeformedImage (DeformedImage) + alphaBytes: DeformedImageBytes, + isResubmit: boolean + ) { + super(imageId, error, modeStatus, ebtsImageId, isResubmit); + this.responseStatus = responseStatus; + this.alphaBytes = alphaBytes; + } +} + +export class DeformedImageBytes { + + deformedId: number; + imageData: number[]; + // Score is the Confidence Score + score: number; + imageType: string; + width: number; + height: number; + + constructor( + deformedId: number, + imageData: number[], + score: number, + imageType: string, + width: number, + height: number + ){ + this.deformedId = deformedId; + this.imageData = imageData; + this.score = score; + this.imageType = imageType; + this.width = width; + this.height = height; + } +} + +export class DeformedImageData { + + imageData: ImageData; + // Score is the Confidence Score + score: number; + imageType: string; + width: number; + height: number; + + constructor( + imageData: ImageData, + score: number, + imageType: string, + width: number, + height: number + ){ + this.imageData = imageData; + this.score = score; + this.imageType = imageType; + this.width = width; + this.height = height; + } + +} diff --git a/web/client/src/app/pupil-dilation-tool/pupil-dilation-tool.component.css b/web/client/src/app/pupil-dilation-tool/pupil-dilation-tool.component.css new file mode 100644 index 0000000..75aab7c --- /dev/null +++ b/web/client/src/app/pupil-dilation-tool/pupil-dilation-tool.component.css @@ -0,0 +1,19 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +.cancel-button { + background-color: #707070; + color: white; +} + +.pdm-note { + float: right; + font-size: 12px; +} \ No newline at end of file diff --git a/web/client/src/app/pupil-dilation-tool/pupil-dilation-tool.component.html b/web/client/src/app/pupil-dilation-tool/pupil-dilation-tool.component.html new file mode 100644 index 0000000..43a6a09 --- /dev/null +++ b/web/client/src/app/pupil-dilation-tool/pupil-dilation-tool.component.html @@ -0,0 +1,22 @@ + + +
+ Alpha Score*: + + + +
+ +
+ *Minimum Alpha Score: {{minValue}}
+ *Maximum Alpha Score: {{maxValue}} +
diff --git a/web/client/src/app/pupil-dilation-tool/pupil-dilation-tool.component.ts b/web/client/src/app/pupil-dilation-tool/pupil-dilation-tool.component.ts new file mode 100644 index 0000000..be9fe99 --- /dev/null +++ b/web/client/src/app/pupil-dilation-tool/pupil-dilation-tool.component.ts @@ -0,0 +1,97 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ +import { Component} from '@angular/core'; +import { IrisDisplayComponent } from '../iris-display/iris-display.component'; +import { PupilDilationToolService } from './pupil-dilation-tool.service'; +import { AnnotationType } from '../types/annotation'; +import { MatDialog } from '@angular/material/dialog'; +import { ToolManagerService } from '../iris-display/tool-manager.service'; +import { PdmConfirmationDialogComponent } from '../pdm-confirmation-dialog/pdm-confirmation-dialog.component'; + +@Component({ + selector: 'app-pupil-dilation-tool', + templateUrl: './pupil-dilation-tool.component.html', + styleUrls: ['./pupil-dilation-tool.component.css'] +}) +export class PupilDilationToolComponent{ + static icon = 'all_out'; + static id = 'Pupil Dilation'; + static type: AnnotationType = AnnotationType.PDM; + static iconType = "material"; + + userParentGroupId: string; + + alphaScore = 0.45; + fromRedo = false; + autoCorrect = false; + minValue = 0.2; + maxValue = 0.7; + + constructor( + public irisDisplayComponent: IrisDisplayComponent, + public pdmToolService: PupilDilationToolService, + private toolManager: ToolManagerService, + private matDialogService: MatDialog, + ) { + } + + onApply() { + this.autoCorrectValue(); + this.irisDisplayComponent.getPdmResponses(PupilDilationToolComponent.id, PupilDilationToolComponent.type, PupilDilationToolComponent.icon, PupilDilationToolComponent.iconType, false, this.alphaScore.toString()); + } + + autoCorrectValue() { + this.autoCorrect = true; + // Automatically corrects any number above 0.9 to 0.9 and any number below 0.2 to 0.2. + if (this.alphaScore > 0.9) { + this.alphaScore = 0.9; + } else if (this.alphaScore < 0.2) { + this.alphaScore = 0.2 + } + + var alphaString = this.alphaScore.toString(); + if (alphaString.startsWith(".")) { + alphaString = "0" + alphaString; + this.alphaScore = parseFloat(alphaString); + } + } + + onClose() { + this.matDialogService.open(PdmConfirmationDialogComponent).afterClosed().subscribe(selectedChoice => { + switch (selectedChoice) { + case 'confirm': + this.onConfirm(); + break; + case 'cancel': + this.onCancel(); + break; + case 'back': + // Do nothing since the user wants to keep editing the image. + break; + } + }); + } + + // This should keep the applied pdm image and close the tool. + onConfirm() { + this.irisDisplayComponent.inPdmMode = false; + this.irisDisplayComponent.toggleToolBar(false, false); + } + + // This should remove the applied pdm image and close the tool. + onCancel() { + this.irisDisplayComponent.inPdmMode = false; + if (this.irisDisplayComponent.pdmGroupAnnotation) { + this.toolManager.removeItem(this.irisDisplayComponent.pdmGroupAnnotation.id, false, false, false); + } + this.irisDisplayComponent.toggleToolBar(false, false); + } + +} diff --git a/web/client/src/app/pupil-dilation-tool/pupil-dilation-tool.service.ts b/web/client/src/app/pupil-dilation-tool/pupil-dilation-tool.service.ts new file mode 100644 index 0000000..319265f --- /dev/null +++ b/web/client/src/app/pupil-dilation-tool/pupil-dilation-tool.service.ts @@ -0,0 +1,108 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable, OnDestroy } from '@angular/core'; +import { Observable, timer, Subject, Subscription } from 'rxjs'; +import { switchMap, share, retry, takeUntil } from 'rxjs/operators'; +import { HttpClient } from '@angular/common/http'; +import { ServiceStatus } from '../types/service-response'; +import { DeformedImageBytes, DeformedImageData } from './pupil-dilation-response'; + +@Injectable() +export class PupilDilationToolService implements OnDestroy { + private pdmResponseUrl = '/api/service/pdm/'; + public pdmResponseStatus$: Observable; + private stopResponsePolling$ = new Subject(); + public _pdmStatusSubscription: Subscription[] = []; + + constructor( + private http: HttpClient, + ) { + } + + // Pdm Response Methods: + getPdmStatus(ebtsImageId: string, modifiedImageId: string): Observable { + return this.http.get(this.pdmResponseUrl + 'status/' + ebtsImageId + '/' + modifiedImageId); + } + + startPdmResponsePolling(ebtsImageId: string, modifiedImageId: string): void { + this.pdmResponseStatus$ = timer(1, 5000).pipe( //5000 = 5 second polling interval + switchMap(() => + this.getPdmStatus(ebtsImageId, modifiedImageId) + ), + retry(4), + share(), + takeUntil(this.stopResponsePolling$) + ); + } + + getPdmSnapshot(imageData: string, ebtsImageId: string): Observable { + return this.http.post(this.pdmResponseUrl + 'snapshot/' + ebtsImageId, imageData); + } + + processPdm(imageData): Observable { + return this.http.post(this.pdmResponseUrl + 'process', imageData); + } + + stopPdmResponsePolling(imageId?: string): void { + if (imageId) { + this._pdmStatusSubscription[imageId].unsubscribe(); + } else { + this.stopResponsePolling$.next(true); + this.stopResponsePolling$.complete(); + this._pdmStatusSubscription.forEach(subscription => { + subscription.unsubscribe(); + }) + } + } + + getPdmResponse(imageId: string, alphaScore: string): Observable { + var deformedId = imageId + "." + alphaScore; + return this.http.get(this.pdmResponseUrl + 'response/' + deformedId + ); + } + + async convertImageBytesToImage(deformedImageBytes: DeformedImageBytes): Promise<{deformedImage: DeformedImageData, canvas: HTMLCanvasElement, img: HTMLImageElement}> { + var paddedImageData = deformedImageBytes.imageData; + var imageData; + + const imageArray = new Uint8Array(paddedImageData); + const blob = new Blob([imageArray], { type: 'image/png' }); + + const img = await new Promise((resolve, reject) => { + const image = new Image(); + image.onload = () => resolve(image); + image.onerror = (err) => reject(err); + image.src = URL.createObjectURL(blob); + }); + + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext('2d'); + + if (!ctx) { + throw new Error("Can't get 2D context"); + } + ctx.drawImage(img, 0, 0); + + // Get raw pixel data (RGBA values) + imageData = ctx.getImageData(0, 0, img.width, img.height); + + var deformedImage = new DeformedImageData(imageData, deformedImageBytes.score, deformedImageBytes.imageType, deformedImageBytes.width, deformedImageBytes.height); + return {deformedImage, canvas, img}; + } + + ngOnDestroy() { + this.stopPdmResponsePolling(); + } + + +} \ No newline at end of file diff --git a/web/client/src/app/review-export/review-export.component.css b/web/client/src/app/review-export/review-export.component.css new file mode 100644 index 0000000..c9ba8c7 --- /dev/null +++ b/web/client/src/app/review-export/review-export.component.css @@ -0,0 +1,13 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +#button-align-div1 { + float: right; +} diff --git a/web/client/src/app/review-export/review-export.component.html b/web/client/src/app/review-export/review-export.component.html new file mode 100644 index 0000000..595b0f9 --- /dev/null +++ b/web/client/src/app/review-export/review-export.component.html @@ -0,0 +1,81 @@ + +
+
+
+

Review CSV Output

+
+
+ + Start Date + + + + + + End Date + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Comparison Date{{ getDateStringFromEpoch(request.caseInformation.comparisonDate / 1000) }}Transaction Control Number{{ request.probe.transactionControlNumber }}Response Type{{ request.responseType }}# of Candidates{{ request.candidates.length }}Requested By{{ request.caseInformation.requestedBy.name }}Conducted By{{ request.workedBy.profile.name }}Completed{{ request.isFinished }}
+
+
+
diff --git a/web/client/src/app/review-export/review-export.component.spec.ts b/web/client/src/app/review-export/review-export.component.spec.ts new file mode 100644 index 0000000..8174aa7 --- /dev/null +++ b/web/client/src/app/review-export/review-export.component.spec.ts @@ -0,0 +1,82 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ReviewExportComponent } from './review-export.component'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatSortModule } from '@angular/material/sort'; +import { MatTableModule } from '@angular/material/table'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatNativeDateModule } from '@angular/material/core'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { ProfileService } from '../profile/profile.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { FormsModule } from '@angular/forms'; + +describe('XXIII. The ReviewExportComponent class:', () => { + const epoch = 1661187660; + const selectAll = false; + const num = '5'; + const size = 15; + let component: ReviewExportComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + BrowserAnimationsModule, + FormsModule, + HttpClientTestingModule, + RouterTestingModule, + MatCheckboxModule, + MatDatepickerModule, + MatDialogModule, + MatFormFieldModule, + MatInputModule, + MatNativeDateModule, + MatSortModule, + MatPaginatorModule, + MatTableModule + ], + declarations: [ReviewExportComponent], + providers: [ProfileService, MatDialogModule, MatPaginatorModule], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ReviewExportComponent); + component = fixture.componentInstance; + + fixture.detectChanges(); + }); + + it(`a. getDateStringFromEpoch takes an epoch date as a parameter, like ${epoch}, and returns that epoch as a string.`, () => { + const res = component.getDateStringFromEpoch(epoch); + expect(res).toBeInstanceOf(String); + }); + + it(`b. toggleSelectAllText takes boolean as a parameter and returns the changed selectAll status and text (technically a void type).`, () => { + const res = component.toggleSelectAllText(selectAll); + expect(res).toBeUndefined(); + }); + + it(`c. pad takes a number to pad (e.g., ${num}) and a padding size (e.g., ${size}) as parameters, and then pads that number with ${size-1} leading zeros, i.e., '000000000000005'.`, () => { + const res = component.pad(num,size); + expect(res).toBeInstanceOf(String); + }); +}); diff --git a/web/client/src/app/review-export/review-export.component.ts b/web/client/src/app/review-export/review-export.component.ts new file mode 100644 index 0000000..49a8ae0 --- /dev/null +++ b/web/client/src/app/review-export/review-export.component.ts @@ -0,0 +1,180 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component, OnInit, ViewChild } from '@angular/core'; +import { DatePipe } from '@angular/common'; +import { MatTableDataSource } from '@angular/material/table'; +import { ReportData } from '../types/report-data'; +import { HistoryService } from '../history/history.service'; +import { ProfileService } from '../profile/profile.service'; +import { MatSort } from '@angular/material/sort'; +import { MatPaginator } from '@angular/material/paginator'; +import { ReviewExportService } from './review-export.service'; + +@Component({ + selector: 'app-review-export', + templateUrl: './review-export.component.html', + styleUrls: ['./review-export.component.css'], + providers: [DatePipe, MatSort] +}) + +export class ReviewExportComponent implements OnInit { + reviewRequests: Array = []; + idList: Array = []; + reviewRequestData: MatTableDataSource; + originalReviewRequestData: MatTableDataSource; + reviewStart = 0; + allSelected = false; + reviewLength = 0; + stepSize = 20; + + reviewSortColumn = 'caseInformation.comparisonDate'; + reviewRequestColumns = ['selected', 'comparisonDate', 'transactionControlNumber', 'responseType', + 'candidateCount', 'requestedBy', 'reviewedBy', 'isFinished']; + caretScaling = 'caret-descend'; + + // @ViewChild is a reference that injects the + // child component into a given parent component. + @ViewChild('reviewSort') reviewRequestSort: MatSort; + @ViewChild('reviewPaginator', {static: true}) reviewPaginator: MatPaginator; + + startDateVal: Date; + endDateVal: Date; + + constructor( + private historyService: HistoryService, + public profileService: ProfileService, + private reviewService: ReviewExportService) { + } + + ngOnInit() { + this.historyService.getReviewCount().subscribe((len) => { + this.reviewLength = len; + }); + + const lastMonth = new Date(); + lastMonth.setDate(lastMonth.getDate() - 30); + this.startDateVal = lastMonth; + this.endDateVal = new Date(); + + const dir = this.caretScaling == 'caret-descend' ? 'desc' : 'asc'; + + this.historyService.getAllReviewList(this.reviewStart, this.stepSize, this.reviewSortColumn, dir) + .subscribe((requests) => { + + this.reviewRequests = requests; + this.reviewRequestData = new MatTableDataSource(requests); + this.reviewRequestData.sortingDataAccessor = (item, property) => { + let value; + + switch (property) { + case 'comparisonDate': + value = item.caseInformation.comparisonDate; + break; + case 'transactionControlNumber': + value = item.probe.transactionControlNumber; + break; + case 'candidateCount': + value = item.candidates.length; + break; + case 'requestedBy': + value = item.caseInformation.requestedBy.name; + break; + case 'reviewedBy': + value = item.workedBy.profile.name; + break; + default: + value = item[property]; + } + + return value; + }; + this.reviewRequestData.paginator = this.reviewPaginator; + this.reviewRequestData.sort = this.reviewRequestSort; + }); + } + + applyDateFilter() { + this.idList = []; + this.toggleSelectAllText(false); + + this.reviewRequestData = new MatTableDataSource(this.reviewRequests); + this.reviewRequestData.data = this.reviewRequestData.data.filter(e => + // Add a day in milliseconds to the end date to make the filter include the entire selected day + e.caseInformation.comparisonDate > this.startDateVal.valueOf() && e.caseInformation.comparisonDate < (this.endDateVal.valueOf() + 8.64e+7) + ); + } + + getDateStringFromEpoch(epochDate: number): string { + const d = new Date(0); + d.setUTCSeconds(epochDate); + return d.toLocaleString(); + } + + addToList(i: string, $event) { + if (!$event.checked) { + this.toggleSelectAllText(false); + this.idList.splice(this.idList.indexOf(i), 1); + } else { + this.idList.push(i); + } + } + + toggleSelectAllText(selectAll: boolean) { + if (selectAll) { + this.allSelected = true; + } else { + this.allSelected = false; + } + } + + toggleAll($event) { + this.idList = []; + + if ($event.checked) { + for (const temp of this.reviewRequestData.data) { + this.idList.push(temp.id); + } + } + + this.toggleSelectAllText($event.checked); + } + + createCSV() { + if (this.idList.length > 0) { + this.reviewService.pullReport(this.idList).subscribe(data => { + const blob = new Blob([data], {type: 'text/csv'}); + //create the link element + const a = document.createElement('a'); + document.body.appendChild(a); + + //set the fields + const objectUrl = URL.createObjectURL(data); + a.href = objectUrl; + const date = new Date(); + a.download = this.pad(date.getMonth() + 1, 2) + '.' + this.pad(date.getDate(), 2) + '.' + date.getFullYear() + '-' + + this.pad(date.getHours(), 2) + '.' + this.pad(date.getMinutes(), 2) + '.' + this.pad(date.getSeconds(), 2) + '.csv'; + a.click(); + window.URL.revokeObjectURL(objectUrl); + }, + error => { + console.log(error); + }); + } + } + + pad(num, size): string { + let s = num + ''; + while (s.length < size) { + s = '0' + s; + } + return s; + } +} diff --git a/web/client/src/app/review-export/review-export.resolver.ts b/web/client/src/app/review-export/review-export.resolver.ts new file mode 100644 index 0000000..d5acba5 --- /dev/null +++ b/web/client/src/app/review-export/review-export.resolver.ts @@ -0,0 +1,31 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; +import { Resolve } from '@angular/router'; + +import { ProfileService } from '../profile/profile.service'; +import { HttpClient } from '@angular/common/http'; + +@Injectable() +export class ReviewExportResolver implements Resolve> { + + configFileUrl = './iris_review_config.json'; + + constructor(private http: HttpClient, private profileService: ProfileService) { + } + + resolve(): Promise { + this.profileService.getProfile().subscribe(prfs => { + // Do nothing. simply a session alive check + }); + return this.http.get(this.configFileUrl).toPromise().then(r => r); + } +} diff --git a/web/client/src/app/review-export/review-export.service.spec.ts b/web/client/src/app/review-export/review-export.service.spec.ts new file mode 100644 index 0000000..5c3eb32 --- /dev/null +++ b/web/client/src/app/review-export/review-export.service.spec.ts @@ -0,0 +1,27 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { TestBed } from '@angular/core/testing'; +import { Observable } from 'rxjs'; +import { ReviewExportService } from './review-export.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; + +describe('XXIV. The ReviewExportService class:', () => { + const ids = ['12345', '67890']; + beforeEach(() => TestBed.configureTestingModule({ + imports: [HttpClientTestingModule] + })); + + it('a. pullReport takes an array of strings as a parameter and returns an Observable Blob.', () => { + const service: ReviewExportService = TestBed.inject(ReviewExportService); + const res = service.pullReport(ids); + expect(res).toBeInstanceOf(Observable); + }); +}); diff --git a/web/client/src/app/review-export/review-export.service.ts b/web/client/src/app/review-export/review-export.service.ts new file mode 100644 index 0000000..00768b0 --- /dev/null +++ b/web/client/src/app/review-export/review-export.service.ts @@ -0,0 +1,33 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class ReviewExportService { + + getCsvUrl = '/api/iris/report/csv/download'; + + constructor( + private http: HttpClient + ) { + } + + pullReport(ids: string[]): Observable { + return this.http.get(this.getCsvUrl, { + params: new HttpParams().set('id', ids.join(',')), + responseType: 'blob' + }); + } +} diff --git a/web/client/src/app/review/candidate-container.component.ts b/web/client/src/app/review/candidate-container.component.ts new file mode 100644 index 0000000..b48d666 --- /dev/null +++ b/web/client/src/app/review/candidate-container.component.ts @@ -0,0 +1,181 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { + Component, Directive, ViewContainerRef, Output, EventEmitter, + ComponentFactoryResolver, ViewChild, Input, OnChanges, SimpleChanges + } from '@angular/core'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { ReviewContentService } from './review.service'; +import { IrisDisplayComponent } from '../iris-display/iris-display.component'; +import { ContentService } from '../utils/content.service'; +import { EbtsImageData } from '../types/image-data'; +import { Annotation } from '../types/annotation'; +import { State } from '../types/state'; + + +@Directive({ + selector: '[appViewCandidate]' +}) +export class ViewCandidateDirective { + constructor(public viewContainerRef: ViewContainerRef) { + } +} + +@Component({ + selector: 'app-candidate-container', + template: ` + ` +}) +export class CandidateContainerComponent implements OnChanges { + + @ViewChild(ViewCandidateDirective, {static: true}) viewCandidate: ViewCandidateDirective; + @Input() uuid: string; + @Input() iris: HTMLImageElement; + @Input() currentImageData: EbtsImageData; + @Input() unrollImagePath = ''; + @Input() unrollRingData: object; + @Input() displayDrawColor: string; + @Input() displayFillColor: string; + @Input('annotations') originalAnnotations: Annotation[]; + @Input() showOriginal: BehaviorSubject; + @Input() showUnrolled: BehaviorSubject; + @Input() isVertical: BehaviorSubject; + @Input() saved: Subject; + @Input() pastStates: State[]; + @Input() currState: State; + @Input() isReview = false; + @Input() sourceName: string; + @Input() loadedCandidateImage: any; + @Input() loadedProbeImage: any; + @Output('irismousemove') mouseMove: EventEmitter = new EventEmitter(true); + @Output() resetEmitter = new EventEmitter(true); + @Output() cacheEmitter = new EventEmitter(true); + @Output('updateUnroll') updateUnrollEmitter: EventEmitter = new EventEmitter(true); + + private _lastUuid = ''; + private reviewSvc: ReviewContentService; + + constructor(private contentSvc: ContentService, + private componentFactoryResolver: ComponentFactoryResolver) { + this.reviewSvc = (this.contentSvc); + } + + ngOnChanges(changes: SimpleChanges) { + if (changes.uuid != undefined) { + const uuid = changes.uuid.currentValue; + + if (uuid !== this._lastUuid) { + this.onCandidateChange(uuid); + this.cacheEmitter.emit(uuid); + this._lastUuid = uuid; + } + } + } + + updateUnroll() { + this.updateUnrollEmitter.emit(this.uuid); + } + + resetCurrentDisplay() { + // remove the current View instance of the corresponding image/uuid pair + const vcf = this.viewCandidate.viewContainerRef; + vcf.remove(); + + this.createIrisDisplayComponent(this._lastUuid, true); + this.resetEmitter.emit(this.uuid); + } + + clearCurrentDisplayAnnotations() {// remove the current View instance of the corresponding image/uuid pair + const vcf = this.viewCandidate.viewContainerRef; + vcf.remove(); + + this.createIrisDisplayComponent(this._lastUuid, false); + } + + createIrisDisplayComponent(uuid, loadOriginalAnnotations: boolean) { + const vcf = this.viewCandidate.viewContainerRef; + + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(IrisDisplayComponent); + + const componentRef = vcf.createComponent(componentFactory); + + // Bind the iris image to the [iris] @Input of the IrisDisplayComponent + (componentRef.instance).iris = this.iris; + (componentRef.instance).tshepiiHidden = false; + (componentRef.instance).pdmModifiedId = this.currentImageData.ebtsImageId; + (componentRef.instance).currentAutomatedAnnotationId = this.currentImageData.ebtsImageId; + (componentRef.instance).sourceName = this.sourceName; + + + if (loadOriginalAnnotations) { + (componentRef.instance).originalAnnotations = this.originalAnnotations; + } + + // Set probeId, candidateId, and current image data + (componentRef.instance).currentImageData = this.currentImageData; + (componentRef.instance).loadedProbeImage = this.loadedProbeImage; + (componentRef.instance).loadedCandidateImage = this.loadedCandidateImage; + + // Set draw and fill colors for candidate components + (componentRef.instance).displayDrawColor = this.displayDrawColor; + (componentRef.instance).displayFillColor = this.displayFillColor; + + // Set unroll info + (componentRef.instance).unrollImagePath = this.unrollImagePath; + (componentRef.instance).unrollRingData = this.unrollRingData; + + // Set view info + (componentRef.instance).showOriginal = this.showOriginal; + (componentRef.instance).showUnrolled = this.showUnrolled; + (componentRef.instance).isReview = this.isReview; + + + // Set orientation + (componentRef.instance).isVertical = this.isVertical; + (componentRef.instance).saved = this.saved; + + (componentRef.instance).pastStates = this.pastStates; + (componentRef.instance).currState = this.currState; + + // Set tshepii annotations + if (this.reviewSvc.checkAnnotationsForTshepii) { + this.currentImageData.annotations.forEach((anno: Annotation) => { + if (anno.text === "Tshepii") { + let tshepiiAnnotations = [] + anno.tempChildren.forEach((child: Annotation) => { + tshepiiAnnotations.push(child); + }); + (componentRef.instance).tshepiiAnnotations = tshepiiAnnotations; + } + }); + } + + // Listen for the (reset) @Output EventEmitter of the IrisDisplayComponent + (componentRef.instance).resetEmitter.subscribe(evt => this.resetCurrentDisplay()); + (componentRef.instance).clearEmitter.subscribe(evt => this.clearCurrentDisplayAnnotations()); + (componentRef.instance).updateUnrollEmitter.subscribe(evt => this.updateUnroll()); + + (componentRef.instance).mouseMove.subscribe(evt => this.mouseMove.emit(evt)); + + this.reviewSvc.cache(uuid, componentRef); + } + + onCandidateChange(uuid) { + const vcf = this.viewCandidate.viewContainerRef; + vcf.detach(); + + if (!this.reviewSvc.hasCached(uuid)) { + this.createIrisDisplayComponent(uuid, true); + } else { + vcf.insert(this.reviewSvc.cache(uuid).hostView); + } + } +} diff --git a/web/client/src/app/review/report-details.component.html b/web/client/src/app/review/report-details.component.html new file mode 100644 index 0000000..3cdca56 --- /dev/null +++ b/web/client/src/app/review/report-details.component.html @@ -0,0 +1,158 @@ + +
+
+
Info
+
+
+ + Comparison Date + + Comparison Date must not be a future date. + + + + + Received Date + + Received Date must not be a future date. + + + +
+
+
+ +
+
Conducted By
+
+
+ + Name + + Entry must be 2-40 alpha characters. + + + Email + + The email is invalid. email@example.com + + + Phone + + The entry is invalid. xxx-xxx-xxxx + + + Title + + Entry must be less than 40 characters. + + + Department + + Entry must be less than 40 characters. + + + Address + + Entry must be less than 100 characters. + +
+
+
+ +
+
Requested By + + + + No Selection + + + {{person.name}} + + + +
+
+
+ + Name + + + This field is required. + Name must be 2-40 alpha characters. {{requestForm.invalid}} + + + + Email + + The email is invalid. Example: email@example.com + + + Phone + + The entry is invalid. Please use the format: xxx-xxx-xxxx + + + Title + + Entry must be less than 40 characters. + + + Department + + Entry must be less than 40 characters. + + + Address + + Entry must be less than 100 characters. + +
+
+
+
diff --git a/web/client/src/app/review/report-details.component.ts b/web/client/src/app/review/report-details.component.ts new file mode 100644 index 0000000..2d4db21 --- /dev/null +++ b/web/client/src/app/review/report-details.component.ts @@ -0,0 +1,113 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { NgForm } from '@angular/forms'; +import { PersonService } from '../person/person.service'; +import { ProfileService } from '../profile/profile.service'; +import { Profile } from '../profile/profile'; +import { Person } from '../types/person'; +import { CaseInformation } from '../types/case-information'; + +@Component({ + selector: 'app-report-details', + templateUrl: 'report-details.component.html', + styles: [` + `] +}) +export class ReportDetailsComponent implements OnInit{ + persons: Person[] = []; + currentProfile: Profile; + selected: any; + noSelectionRequestPerson = new Person(); + isNameRequired = false; + isAutoSelected = false; + initDate = new Date(); + comparisonDate = new Date(); + receivedDate = new Date(); + + @Input() caseInformation: CaseInformation; + @ViewChild(NgForm, {static: true}) reportForm; + @ViewChild(NgForm, {static: true}) requestForm; + + + constructor( + public personService: PersonService, + private profileService: ProfileService + ) { + this.personService.getPersons().subscribe(persons => { + this.persons = persons; + }); + + this.noSelectionRequestPerson.name = ''; + this.noSelectionRequestPerson.email = ''; + this.noSelectionRequestPerson.phone = ''; + this.noSelectionRequestPerson.title = ''; + this.noSelectionRequestPerson.department = ''; + this.noSelectionRequestPerson.address = ''; + this.noSelectionRequestPerson.id = ''; + + this.currentProfile = this.profileService.getCurrentProfile(); + } + + ngOnInit() { + this.selected = 'No Selection'; + this.caseInformation.requestedBy = this.noSelectionRequestPerson; + + const conductedByPerson = new Person(); + conductedByPerson.name = this.currentProfile.fullName; + conductedByPerson.title = this.currentProfile.title; + conductedByPerson.department = this.currentProfile.department; + conductedByPerson.address = this.currentProfile.address; + conductedByPerson.email = this.currentProfile.email; + conductedByPerson.phone = this.currentProfile.phoneNumber; + conductedByPerson.id = this.currentProfile.id; + + this.caseInformation.conductedBy = conductedByPerson; + } + + setComparisonFormField($event) { + this.caseInformation.comparisonDate = $event.getTime(); + } + + setReceivedFormField($event) { + this.caseInformation.receivedDate = $event.getTime(); + } + + conductedBySelected(selectedPerson: Person) { + this.caseInformation.conductedBy = selectedPerson; + } + + requestedBySelected(selectedPerson: Person) { + if (selectedPerson != this.noSelectionRequestPerson) { + this.isAutoSelected = true; + this.isNameRequired = false; + } else { + this.isAutoSelected = false; + } + this.caseInformation.requestedBy = selectedPerson; + } + + checkRequesterFields() { + if (this.caseInformation.requestedBy == this.noSelectionRequestPerson) { + if (this.caseInformation.requestedBy.name != '') { + this.caseInformation.requestedBy.id = '0'; + this.isNameRequired = false; + } else { + if (this.caseInformation.requestedBy.email != '' || this.caseInformation.requestedBy.phone != '' || this.caseInformation.requestedBy.title != '' || this.caseInformation.requestedBy.department != '' || this.caseInformation.requestedBy.address != '') { + this.isNameRequired = true; + } else { + this.isNameRequired = false; + } + } + } + } + +} diff --git a/web/client/src/app/review/response-review.service.spec.ts b/web/client/src/app/review/response-review.service.spec.ts new file mode 100644 index 0000000..9bd0c90 --- /dev/null +++ b/web/client/src/app/review/response-review.service.spec.ts @@ -0,0 +1,35 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { TestBed, inject } from '@angular/core/testing'; +import { ResponseReviewService } from './response-review.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ReportData } from '../types/report-data'; +import { Observable } from 'rxjs'; + +describe('XXII. The ResponseReviewService class:', () => { + + const report_data = new ReportData(); + report_data.fileName = 'john_doe_wrapsheet.docx'; + report_data.id = '12345'; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ResponseReviewService] + }); + }); + + + it(`a. createReport takes an object instance of the ReportData class, like ${JSON.stringify(report_data)}, and returns Observable ReportData.`, inject([ResponseReviewService], (service: ResponseReviewService) => { + const res = service.createReport(report_data); + expect(res).toBeInstanceOf(Observable); + })); +}); diff --git a/web/client/src/app/review/response-review.service.ts b/web/client/src/app/review/response-review.service.ts new file mode 100644 index 0000000..293b6e9 --- /dev/null +++ b/web/client/src/app/review/response-review.service.ts @@ -0,0 +1,45 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; +import { ReportData } from '../types/report-data'; +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { State } from '../types/state'; + +@Injectable() +export class ResponseReviewService { + + reportServiceUrl = '/api/iris/report'; + + constructor( + private http: HttpClient + ) { + } + + createReport(reportData: ReportData): Observable { + // Remove pastStates/currState to get rid of cyclic JSON: + + for (const image of reportData.probe.imageList) { + image.pastStates = new Array(); + image.currState = new State(); + } + + for (const candidate of reportData.candidates) { + for (const cimage of candidate.imageList) { + cimage.pastStates = new Array(); + cimage.currState = new State(); + } + } + + return this.http.post(this.reportServiceUrl, reportData); + } + +} diff --git a/web/client/src/app/review/review.component.css b/web/client/src/app/review/review.component.css new file mode 100644 index 0000000..3afbc17 --- /dev/null +++ b/web/client/src/app/review/review.component.css @@ -0,0 +1,148 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +.h3 { + vertical-align: middle +} + +.image-mode-navbar-wrapper { + padding: 0; +} + +.image-mode-navbar { + border: none; + background: none; + box-shadow: none; + margin: 0; + padding: 0; +} + +.image-mode-navbar > .container-fluid, +.image-mode-navbar > .container-fluid > .navbar-collapse { + padding: 0; +} + +.fill-space { + flex: 1 1 auto; +} + +#help { + text-align: center; +} + +#help > .guide { + display: inline-block; +} + +#help > .guide ~ .guide { + margin-left: 15px; +} + +#candidate-btn-group > button { + padding: 0px 4px +} + +#candidate-btn-group.horizontalMode { + position: absolute; + left: -12px; + width: 25px; + top: 100px; +} + +#candidate-btn-group.verticalMode { + text-align: center; +} + +#probe-v-spacer { + height: 48px; +} + +.card { + border-top-left-radius: 0; +} + +.toggle-image-btns > button { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom: none; +} + +.toggle-image-btns > button.btn-primary { + background-image: none; + background-color: #e0e0e0; +} + +.toggle-image-btns > button ~ button { + margin-left: 1px; +} + +.dropzone { + height: 100%; +} + +.toolbar-btn { + height: 38px; +} + +.review-toolbar { + height: 48px; +} + +.review-toolbar > div { + flex: 1; + display: flex; +} + +.tshepii-button { + flex: 0; +} + +.tshepii-status { + font-size: 12px; + margin-right: 15px; + margin-left: 3px; +} + +.dualPdm-button { + flex: 0; +} + +.dualPdm-status { + font-size: 12px; + margin-right: 15px; + margin-left: 3px; +} + +.review-toolbar .review-controls { + justify-content: flex-end; +} + +.custom-select { + min-width: 130px; +} + +.unload-button-visible { + width: calc(100% - 75px) !important; +} + +.cancel-button { + background-color: rgb(112, 112, 112); + color: white; + margin-right: 5px; +} + +.modal-footer { + justify-content: space-between; +} + +.right-footer { + justify-content: flex-end; + align-items: center; +} \ No newline at end of file diff --git a/web/client/src/app/review/review.component.html b/web/client/src/app/review/review.component.html new file mode 100644 index 0000000..79e1c4a --- /dev/null +++ b/web/client/src/app/review/review.component.html @@ -0,0 +1,537 @@ + +
+ +
+ Review +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + + + view_week + + + view_stream + + +
+
+ +
+ + + +
+
+
+ +
+
+ +
+
+
+ Probe + +
+
+ + + + + Probe Information + + +
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+ + + + Record Type: {{imageData.recordType}}
Image No.{{imageData.recordIndex}}
+ +
+
+ + + + + Image Information + + +
+ + +
+
+ + + +
Analysis believes this is + {{eyeLabel[reviewService.probe.analysisPosition]}} + Eye +
+
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+ +
+ +
+
+ +

Resetting...

+
+ + + + +
+
Probe Image Has Data Missing! +
+
+
+
+ + + +
+
+
+ Candidates + +
+
+ + + + + Select Candidate + + + + + +
+
+ +
+
+ + + + Record Type: {{imageData.recordType}}
Image No.{{imageData.recordIndex}}
+ + + + +
+
+ + + + + Image Information + + +
+ + +
+
+ + + +
Analysis believes this is + {{eyeLabel[reviewService.candidate.analysisPosition]}} Eye +
+
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+ +
+ +
+
+ +
+
+

Please select a candidate above.

+
+
+
+ +

Resetting...

+
+ + + +
This Image is Identical to the + Probe! +
+
+
+
Candidate Image Has Data + Missing! +
+
+ +
+ +
+
+ +
+
+

+ Move - + Shift + Mouse Drag +

+

+ Zoom - + Mouse Wheel +

+
+
+ + + + +
+ + + +
+
+ + + + +
+ + + +
+
+ +
+
diff --git a/web/client/src/app/review/review.component.ts b/web/client/src/app/review/review.component.ts new file mode 100644 index 0000000..6ccce82 --- /dev/null +++ b/web/client/src/app/review/review.component.ts @@ -0,0 +1,1399 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component, ElementRef, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { BehaviorSubject, Subject, Subscription } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { ProfileService } from '../profile/profile.service'; +import { ReviewContentService } from './review.service'; +import { ContentService } from '../utils/content.service'; +import { ResponseType } from '../types/report-data'; +import { ResponseReviewService } from './response-review.service'; +import { Candidate } from '../types/candidate'; +import { Probe } from '../types/probe'; +import { EyeLabel } from '../types/eye-label'; +import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; +import { NgForm } from '@angular/forms'; +import { EbtsImageData } from '../types/image-data'; +import { DialogService } from '../utils/dialog.serivce'; +import { UnrollCompareDialogComponent } from './unroll-compare/unroll-compare-dialog.component'; +import { HistoryService } from '../history/history.service'; +import { SaveDialogComponent } from '../save-dialog/save-dialog.component'; +import { TshepiiService } from '../tshepii/tshepii.service'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { CaseInformation } from '../types/case-information'; +import { Person } from '../types/person'; +import { ServiceStatus } from '../types/service-response'; +import { AnnotationComponent } from '../annotation-dialog/annotation.component'; +import { ContactType } from '../types/contact-type'; +import { PdmConfirmationDialogComponent } from '../pdm-confirmation-dialog/pdm-confirmation-dialog.component'; + +enum ReviewStatus { + FILE_LOAD, + FULL_RESPONSE, + RESETTING, + ACTIVE_CANDIDATE +} + +@Component({ + templateUrl: 'review.component.html', + styleUrls: ['./review.component.css'], + providers: [ + {provide: ContentService, useClass: ReviewContentService}, + ] +}) +export class ReviewComponent implements OnInit, OnDestroy { + config: any; + probeConfig: any = {}; // copy of config with probe-specific tweaks + + loadedFromSave = false; + colors: any; + candidateStatus = ReviewStatus.FILE_LOAD; + reviewStatus = ReviewStatus; //this is so you can reference review status within the template + eyeLabel = EyeLabel; + eyeLabelOptions = Object.keys(EyeLabel).splice(0, Object.keys(EyeLabel).length / 2); + contactType = ContactType; + contactTypeOptions = Object.keys(ContactType).splice(0, Object.keys(ContactType).length / 2); + + redirectUrl = ''; + singleImageProbeLoaded = false; + singleImageCandidateLoaded = false; + isCandidatePanelExpanded: boolean; + okayToNavigate = false; + + loadedUUIDs: any[] = []; + persons: Person[] = []; + isSystemGeneratedAnnotationsIncluded = true; + + probeSaved = new Subject(); + candidateSaved = new Subject(); + + @ViewChild('errorModal', {static: true}) errorModal: TemplateRef; + @ViewChild('reportFieldForm', {static: true}) reportFieldForm: NgForm; + @ViewChild('reportDetails') reportDetails: ElementRef; + @ViewChild('saveDialog', {static: true}) saveDialog: SaveDialogComponent; + @ViewChild('reviewForm', {static: true}) reviewForm: NgForm; + candidateSelectedTab = 0; + probeSelectedTab = 0; + + canSubmit = false; + canUnloadCandidate = false; + canUnloadProbe = false; + canUnloadFile = false; + tshepiiLoading = false; + counter = 0; + hasSaveButtonSelected = false; + isProbeIrisDataMissing = false; + isCandidateIrisDataMissing = false; + + // Prevent users from loading file, unloading, and accidentally overwriting saved files on reset + savedReviewFileReset = false; + saveInProgress = false; + + tempImageData: EbtsImageData; + + testImageData: any; + + probeShowOrigSubject = new BehaviorSubject(true); + probeShowUnrolledSubject = new BehaviorSubject(false); + candShowOrigSubject = new BehaviorSubject(true); + candShowUnrolledSubject = new BehaviorSubject(false); + probeStatusSubject = new Subject(); + candStatusSubject = new Subject(); + // Replace these BSmodals with MatDialog! + previewModalRef: BsModalRef; + compareModalRef: MatDialogRef; + isGhostCursorOn = false; + isVertical = false; + isVerticalSubject = new BehaviorSubject(false); + isTshepiiCompareReady = false; + _tshepiiResponseSubscription: Subscription; + _contactResponseSubscription: Subscription[] = []; + annotationComp: AnnotationComponent; + _aciiResponseSubscription: Subscription[] = []; + _biqtResponseSubscription: Subscription[] = []; + adjudicationOptions: string[] = []; + + get canShowGhostCursor(): boolean { + return this.reviewService.probe.status === 'loaded' + && this.reviewService.currentCandidate !== null + && this.candidateStatus == ReviewStatus.ACTIVE_CANDIDATE; + } + + constructor( + private route: ActivatedRoute, + private modalService: BsModalService, + private contentSvc: ContentService, + private responseReviewService: ResponseReviewService, + private profileService: ProfileService, + private historyService: HistoryService, + private dialogSvc: DialogService, + private tshepiiService: TshepiiService, + private router: Router, + private matDialogService: MatDialog, + private matSnackBar: MatSnackBar, + public reviewService: ReviewContentService, + ) { + + } + + ngOnInit() { + // Route Resolver for config file + this.route.data.subscribe(data => { + this.config = data.config; + }); + + // load adjudication values + this.reviewService.getAdjudicationOptions().subscribe(ret => { + this.adjudicationOptions = ret; + }); + + this.contentSvc.configure(this.config); + this.config = Object.assign(this.config, this.contentSvc.config); + + this.probeConfig = Object.assign(this.probeConfig, this.config); + this.probeConfig.maxFiles = 1; + + this.saveDialog.closed$.subscribe((closed) => { + if (closed) { + if (this.saveDialog.continue) { + if (this.saveDialog.save) { + // Save Off Search + this.finalizeReport(); + this.historyService.saveReview(this.reviewService.currentReport) + .subscribe((response) => { + this.probeSaved.next(this.reviewService.currentReport.probe.imageList[this.reviewService.currentProbeImageIndex].annotations); + if (this.reviewService.currentCandidate) { + this.candidateSaved.next(this.reviewService.currentCandidate.imageList[this.reviewService.currentCandidateImageIndex].annotations); + } + setTimeout(() => { + this.matSnackBar.open('Review saved', 'Dismiss'); + }, 1000); + this.okayToNavigate = true; + this.router.navigateByUrl(this.redirectUrl); + }); + } else { + this.okayToNavigate = true; + this.router.navigateByUrl(this.redirectUrl); + } + } + this.saveDialog.hide(); + } + }); + + //check for case req parameters + this.route.params.subscribe(params => { + if (params['id']) { + if (this.historyService.hasSavedReview) { + this.loadExistingReview(this.historyService.savedReview); + } + } else { + this.isCandidatePanelExpanded = true; + } + }); + } + + loadExistingReview(savedReview: any) { + this.reviewService.currentReport = savedReview; + this.loadedFromSave = true; + + if (this.reviewService.currentReport.probe.imageList.length > 0) { + this.reviewService.currentProbeImageIndex = 0; + this.reviewService.probe.activeUuid = this.reviewService.currentReport.probe.imageList[this.reviewService.currentProbeImageIndex].id; + + if (this.loadedUUIDs.indexOf(this.reviewService.probe.activeUuid) < 0) { + this.loadedUUIDs.push(this.reviewService.probe.activeUuid); + } + + for (const imageData of this.reviewService.currentReport.probe.imageList) { + imageData.eyeLabel = this.reviewService.setEyeLabel(imageData.eyeLabel.toString()); + imageData.contactType = this.getContactType(imageData.contactType.toString()); + } + + this._loadProbeImage(this.reviewService.currentReport.probe.imageList[0]); + } else { + this.reviewService.currentProbeImageIndex = -1; + } + + if (this.reviewService.currentReport.candidates.length > 0) { + for (const candidate of this.reviewService.currentReport.candidates) { + for (const imageData of candidate.imageList) { + imageData.eyeLabel = this.reviewService.setEyeLabel(imageData.eyeLabel.toString()); + imageData.contactType = this.getContactType(imageData.contactType.toString()); + } + } + + this.candidateStatus = ReviewStatus.FULL_RESPONSE; + } + + this.candidateAdjudicated({}); + + if (this.reviewService.currentReport.responseType.toString() == 'SRB' || this.reviewService.currentReport.responseType.toString() == 'XML') { + this.canUnloadFile = true; + } else if (this.reviewService.currentReport.responseType.toString() == 'Image' || this.reviewService.currentReport.responseType.toString() == 'Unknown') { + if (this.reviewService.currentReport.probe.imageList.length > 0) { + this.canUnloadProbe = true; + } + + if (this.reviewService.currentReport.candidates.length > 0) { + this.canUnloadCandidate = true; + } + } + + if (this.reviewService.currentReport.candidates.length > 0) { + this.selectCandidate(this.reviewService.currentReport.candidates[0]); + } + } + + disableSave() { + return !(this.canUnloadProbe || this.canUnloadCandidate || this.canUnloadFile); + } + + checkEyeLabel(userSetEyeLabel: EyeLabel, serviceSetEyeLabel: EyeLabel) { + return userSetEyeLabel == serviceSetEyeLabel; + } + + ngOnDestroy() { + this.historyService.clearSavedReview(); + this.stopTshepiiResponsePolling(); + this._contactResponseSubscription.forEach(subscription => { + subscription.unsubscribe(); + }); + + this._biqtResponseSubscription.forEach(subscription => { + subscription.unsubscribe(); + }); + + this._aciiResponseSubscription.forEach(subscription => { + subscription.unsubscribe(); + }); + + this.unloadFile(); + } + + canDeactivate(url: string) { + if (this.okayToNavigate) { + return true; + } else if ((this.isComponentDirty() && !this.hasSaveButtonSelected) || this.reviewForm.dirty) { + this.redirectUrl = url; + this.saveDialog.show(); + return false; + } else { + return true; + } + } + + isComponentDirty() { + if (this.historyService.hasSavedReview && this.reviewForm.dirty) { + return true; + } + + if (this.loadedFromSave) { + if (this.reviewForm.dirty) { + return true; + } + + // Check each tool manager for modified Iris Images from last save point + const reviewSvc = (this.contentSvc); + for (const item of this.loadedUUIDs) { + if (reviewSvc.cache(item).instance.toolManager.getIrisModified()) { + return true; + } + } + + return false; + } else { + + return (this.reviewService.probe.status !== 'empty') || + (this.candidateStatus !== ReviewStatus.FILE_LOAD) || + (this.reviewService.currentCandidate !== undefined) || + (!!this.reportFieldForm && this.reportFieldForm.dirty); + } + } + + resetAnnos() { + const keyCheck = this.reviewService.probe.activeUuid + this.reviewService.candidate.activeUuid; + this.reviewService._matchMap.delete(keyCheck); + } + + getTshepiiButtonTooltip(): string { + if (!this.canRunTShepiiCompare) { + return 'Load and select both a probe and a candidate to enable TSHEPII Mode.'; + } else { + return this.reviewService._showTshepiiComparison ? 'Exit Tshepii Mode' : 'Enter Tshepii Mode'; + } + } + + checkMatchMap(): void { + this.reviewService.setComparisonMode(!this.reviewService._showTshepiiComparison, this.reviewService.probe, this.reviewService.candidate, this.contentSvc); + if (!this.reviewService._showTshepiiComparison) { + this.reviewService.drawTshepiiComparison(this.reviewService.probe, this.reviewService.candidate, this.contentSvc); + this.reviewService.tshepiiStatusText = ''; + } else { + this.reviewService.tshepiiReturnedResults = false; + const reviewSvc = (this.contentSvc); + let annotations = reviewSvc.getAnnotationsFromCache(); + this.reviewService.getTshepii(this.reviewService.probe, this.reviewService.candidate, this.contentSvc, false, annotations); + } + } + + getDualPDMButtonTooltip(): string { + if (!this.isDualPdmCompareReady()) { + return 'Load and select both a probe and a candidate to enable Dual PDM Mode.'; + } else { + return this.reviewService._showDualPDMComparison ? 'Save Images and Exit Dual PDM Mode' : 'Enter Dual PDM Mode'; + } + } + + triggerDualPdmMode(): void { + // If it's in DualPDMComparison mode (_showDualPDMComparison is true), we want to show the confirmation dialog. We don't want to change the mode view yet since the user could decide to keep working on dual pdm + if (this.reviewService._showDualPDMComparison) { + this.matDialogService.open(PdmConfirmationDialogComponent).afterClosed().subscribe(selectedChoice => { + switch (selectedChoice) { + case 'confirm': + this.onConfirm(); + break; + case 'cancel': + this.onCancel(); + break; + case 'back': + // Do nothing since the user wants to keep editing the image. + break; + } + }); + } + // If it's not in DualPDMComparison mode (_showDualPDMComparison is false), we want to set it put it in DualPDMComparison mode and call getDualPDM() + else { + this.reviewService.setDualPDMComparisonMode(!this.reviewService._showDualPDMComparison, this.reviewService.probe, this.reviewService.candidate, this.contentSvc); + this.reviewService.getDualPDM(this.reviewService.probe.ebtsImageId, this.reviewService.candidate.ebtsImageId, false, null, null); + } + } + + onConfirm() { + // Capture cached annotations and send to getDualPDM for proccessing + this.reviewService.getDualPDM(this.reviewService.probe.ebtsImageId, this.reviewService.candidate.ebtsImageId, false, null, null); + + // Turn off the comparison mode + this.reviewService.setDualPDMComparisonMode(!this.reviewService._showDualPDMComparison, this.reviewService.probe, this.reviewService.candidate, this.contentSvc); + } + + onCancel() { + const reviewSvc = (this.contentSvc); + let probeInstance = reviewSvc.cache(this.reviewService.probe.activeUuid).instance; + let candidateInstance = reviewSvc.cache(this.reviewService.candidate.activeUuid).instance; + + let probeAnnotation = probeInstance.pdmGroupAnnotation; + let candidateAnnotation = candidateInstance.pdmGroupAnnotation; + + // for each probe and candidate, we need to remove the Dual Pdm annotation if there is one. + if (probeAnnotation) { + probeInstance.toolManager.removeItem(probeAnnotation.id, false, false, false); + } + if (candidateAnnotation) { + candidateInstance.toolManager.removeItem(candidateAnnotation.id, false, false, false); + + } + + // Turn off the comparison mode + this.reviewService.setDualPDMComparisonMode(!this.reviewService._showDualPDMComparison, this.reviewService.probe, this.reviewService.candidate, this.contentSvc); + } + + changeCandidateImage() { + if (this.reviewService.currentReport.responseType == ResponseType.Unknown) { + this.singleImageCandidateLoaded = false; + } + + this.stopTshepiiResponsePolling(); + if (this.dialogSvc.confirmBooleam('This will discard the loaded Candidate Image. Proceed?')) { + // Resetting the candidate information if discarding loaded candidate image + this.reviewService.candidate.showInfo = false; + this.reviewService.candidate.imgLoaded = false; + this.reviewService.candidate.ebtsImageId = ''; + this.reviewService.candidate.image = null; + this.reviewService.candidate.unrollImagePath = ''; + this.reviewService.candidate.resetting = false; + this.reviewService.candidate.analysisPosition = EyeLabel['']; + this.reviewService.candidate.activeUuid = ''; + this.reviewService.candidate.unrollRingData = {}; + this.reviewService.candidate.hasPdmResultsReturned = false; + + // Resetting the review service information: + this.reviewService.dualPDMReturnedResults = false; + this.reviewService._showDualPDMComparison = false; + this.reviewService._showTshepiiComparison = false; + this.reviewService.currentReport.candidates.length = 0; + this.reviewService.currentCandidateImageIndex = 0; + this.reviewService.currentCandidate = undefined; + + // Resetting component information + this.candidateStatus = ReviewStatus.FILE_LOAD; + this.canUnloadCandidate = false; + this.isCandidatePanelExpanded = true; + this.isTshepiiCompareReady = false; + + // Need to reset the original and unrolled canvas + this.candShowOrigSubject.next(true); + this.candShowUnrolledSubject.next(false); + + this.candStatusSubject.next(false); + this.candStatusSubject.complete(); + + if (this.historyService.hasSavedReview) { + this.savedReviewFileReset = true; + } + + return true; + } + + return false; + } + + changeProbeImage() { + if (this.reviewService.currentReport.responseType == ResponseType.Unknown) { + this.singleImageProbeLoaded = false; + } + + this.stopTshepiiResponsePolling(); + if (this.dialogSvc.confirmBooleam('This will discard the Probe Image. Proceed?')) { + // Resetting the probe information if discarding probe candidate image + this.reviewService.probe.showInfo = false; + this.reviewService.probe.imgLoaded = false; + this.reviewService.probe.ebtsImageId = ''; + this.reviewService.probe.status = 'empty'; + this.reviewService.probe.activeUuid = ''; + this.reviewService.probe.analysisPosition = EyeLabel['']; + this.reviewService.probe.image = null; + this.reviewService.probe.unrollImagePath = ''; + this.reviewService.probe.unrollRingData = {}; + this.reviewService.probe.hasPdmResultsReturned = false; + + // Resetting the review service information: + this.reviewService.dualPDMReturnedResults = false; + this.reviewService.currentProbeImageIndex = -1; + this.reviewService._showDualPDMComparison = false; + this.reviewService._showTshepiiComparison = false; + + // Resetting component information + this.canUnloadProbe = false; + this.isTshepiiCompareReady = false; + + // Need to reset the original and unrolled canvas + this.probeShowOrigSubject.next(true); + this.probeShowUnrolledSubject.next(false); + + this.probeStatusSubject.next(false); + this.probeStatusSubject.complete(); + this.reviewService.currentReport.probe.imageList = []; + if (this.historyService.hasSavedReview) { + this.savedReviewFileReset = true; + } + + return true; + } + return false; + } + + unloadFileClicked() { + if (this.dialogSvc.confirmBooleam('This will discard the loaded file and all Probe and Candidate Images. Proceed?')) { + this.unloadFile(); + } + } + + unloadFile() { + this.reviewService.currentReport = { + id: '', + fileName: '', + probe: new Probe(), + candidates: new Array(), + responseType: ResponseType.Unknown, + includeExclude: false, + caseInformation: new CaseInformation(), + userId: undefined, + isFinished: false, + errorMessages: Array(), + invalidFields: Array(), + workedBy: undefined, + allowNullRequester: false + }; + + this.reviewService.probe = { + showInfo: false, + imgLoaded: false, + ebtsImageId: '', + status: 'empty', + activeUuid: '', + analysisPosition: EyeLabel[''], + image: null, + unrollImagePath: '', + unrollRingData: {}, + hasPdmResultsReturned: false + }; + + this.reviewService.candidate = { + showInfo: false, + imgLoaded: false, + ebtsImageId: '', + image: null, + unrollImagePath: '', + resetting: false, + analysisPosition: EyeLabel[''], + activeUuid: '', + unrollRingData: {}, + hasPdmResultsReturned: false + }; + + this.reviewService.currentReport.candidates.length = 0; + this.reviewService.currentCandidateImageIndex = 0; + this.reviewService.currentProbeImageIndex = -1; + this.candidateStatus = ReviewStatus.FILE_LOAD; + this.reviewService.currentCandidate = undefined; + this.canUnloadFile = false; + this.isCandidatePanelExpanded = true; + this.reviewService.dualPDMReturnedResults = false; + this.isTshepiiCompareReady = false; + this.reviewService.tshepiiReturnedResults = true; + this.reviewService._showDualPDMComparison = false; + this.reviewService._showTshepiiComparison = false; + + if (this.historyService.hasSavedReview) { + this.savedReviewFileReset = true; + } + } + + openCompareModal() { + const reviewSvc = (this.contentSvc); + + const data = { + + probe: reviewSvc.cache(this.reviewService.probe.activeUuid).instance, + candidate: reviewSvc.cache(this.reviewService.candidate.activeUuid).instance, + + }; + + const unrollCompareConfig = { + data + }; + this.compareModalRef = this.matDialogService.open(UnrollCompareDialogComponent, unrollCompareConfig); + } + + isComparable(): boolean { + if (!!this.reviewService.probe.activeUuid && !!this.reviewService.candidate.activeUuid) { + const reviewSvc = (this.contentSvc); + if (!!reviewSvc.cache(this.reviewService.probe.activeUuid) && !!reviewSvc.cache(this.reviewService.candidate.activeUuid)) { + const probe = reviewSvc.cache(this.reviewService.probe.activeUuid).instance; + const candidate = reviewSvc.cache(this.reviewService.candidate.activeUuid).instance; + return probe.toolManager.hasUnrolled.value && candidate.toolManager.hasUnrolled.value; + } + } + + return false; + } + + candidateAdjudicated(event) { + let result = this.reviewService.currentReport.candidates.length > 0; + for (const candidate of this.reviewService.currentReport.candidates) { + if (candidate.adjudicationResults == undefined) { + result = false; + } + + //This doesn't make sense in normal languages but enums in + //typescript can end up being strings + if (typeof candidate.adjudicationResults == 'string') { + const s: string = candidate.adjudicationResults; + result = s === '' ? false : result; + } + } + this.canSubmit = result; + } + + //this is used to switch between candidates + selectCandidate(candidate) { + this.stopTshepiiResponsePolling(); + this.reviewService.currentCandidate = candidate; + + if(candidate.currentImageIndex != undefined && candidate.currentImageIndex != -1) { + this.reviewService.currentCandidateImageIndex = candidate.currentImageIndex; + this.candidateSelectedTab = candidate.currentImageIndex; + } else { + this.reviewService.currentCandidateImageIndex = 0; + this.reviewService.currentCandidate.currentImageIndex = 0; + this.candidateSelectedTab = 0; + } + + this.isCandidateIrisDataMissing = false; + const selectedCandidate = this.reviewService.currentCandidate.imageList[this.reviewService.currentCandidateImageIndex]; + this.isCandidateIrisDataMissing = this.checkIrisDataMissing(selectedCandidate); + + let tempImageData = this.reviewService.currentCandidate.imageList[this.reviewService.currentCandidateImageIndex]; + let tabCount = 0; + this.reviewService.currentCandidate.imageList.forEach(imageData => { + if(imageData.recordType == 17){ + tempImageData = imageData; + return; + } + + tabCount++; + }); + + this._loadCandidateImage(tempImageData); + if(tempImageData.recordType == 17) { + this.reviewService.currentCandidateImageIndex = tabCount; + this.candidateSelectedTab = tabCount; + } + + this.checkTshepiiResponseStatus(); + this.isCandidatePanelExpanded = false; + } + + //called when the mouse moves on the probe image + probeIrisDisplayMouseMove(point: any) { + if (this.isGhostCursorOn) { + const reviewSvc = (this.contentSvc); + const probeInstance = reviewSvc.cache(this.reviewService.probe.activeUuid).instance; + const candidateInstance = reviewSvc.cache(this.reviewService.candidate.activeUuid).instance; + + candidateInstance.toolManager.showGhostCursor = true; + probeInstance.toolManager.showGhostCursor = false; + + //for the event point coming in it is a general canvas onmousemove event which returns + //x,y in that canvas's coordinate system. paperjs has its own coordinate system that is + //the view's matrix. So you when you take a point from the canvas coordinate system + //paperjs will apply a transformation to change it to its system. By applying the inverse + //matrix will are essentially cancelling that out. + const temp = point.transform(probeInstance.toolManager.project.view.matrix.inverted()); + + candidateInstance.toolManager.ghostCursorPosition = temp; + } + } + + isTshepiiDisabled(): boolean { + if (!this.canRunTShepiiCompare || this.reviewService._showDualPDMComparison) { + return true; + } else { + const reviewSvc = (this.contentSvc); + const probeCache = reviewSvc.cache(this.reviewService.probe.activeUuid); + const candidateCache = reviewSvc.cache(this.reviewService.candidate.activeUuid); + + if (probeCache == null || candidateCache == null) + { + return true; + } + + if (probeCache.instance.inPdmMode || candidateCache.instance.inPdmMode) { + return true; + } + } + return false; + } + + isDualPDMDisabled(): boolean { + if (!this.isDualPdmCompareReady() || this.reviewService._showTshepiiComparison) { + return true; + } else { + const reviewSvc = (this.contentSvc); + const probeCache = reviewSvc.cache(this.reviewService.probe.activeUuid); + const candidateCache = reviewSvc.cache(this.reviewService.candidate.activeUuid); + + if (probeCache == null || candidateCache == null) + { + return true; + } + + if (probeCache.instance.inPdmMode || candidateCache.instance.inPdmMode) { + return true; + } + } + return false; + } + + updateProbeUnroll(uuid: string) { + if (this.reviewService.currentReport.probe.imageList[this.reviewService.currentProbeImageIndex].id == uuid) { + // Reset unroll image id to force unroll update on backend + this.reviewService.currentReport.probe.imageList[this.reviewService.currentProbeImageIndex].unrollImageId = undefined; + } + + this.updateProbeStatus(); + } + + updateCandidateUnroll(uuid) { + if (this.reviewService.currentCandidate.imageList[this.reviewService.currentCandidateImageIndex].id == uuid) { + this.reviewService.currentCandidate.imageList[this.reviewService.currentCandidateImageIndex].unrollImageId = undefined; + } + this.updateCandidateStatus(); + } + + //called when the mouse moves on the candidate image + candidateIrisDisplayMouseMove(point: any) { + if (this.isGhostCursorOn) { + const reviewSvc = (this.contentSvc); + const probeInstance = reviewSvc.cache(this.reviewService.probe.activeUuid).instance; + const candidateInstance = reviewSvc.cache(this.reviewService.candidate.activeUuid).instance; + + candidateInstance.toolManager.showGhostCursor = false; + probeInstance.toolManager.showGhostCursor = true; + + const temp = point.transform(candidateInstance.toolManager.project.view.matrix.inverted()); + + //probeInstance.toolManager.ghostCursorPosition = new Point(event.offsetX, event.offsetY); + probeInstance.toolManager.ghostCursorPosition = temp; + } + } + + //this is just used to load a candidate image + private _loadCandidateImage(imageData: EbtsImageData) { + const uuid = imageData.id; + this.reviewService.candidate.image = new Image(); + this.reviewService.candidate.image.crossOrigin = 'anonymous'; + + this.reviewService.candidate.imgLoaded = false; + + this.reviewService.candidate.unrollImagePath = `${this.config.imageURL}${imageData.unrollImageId}`; + this.reviewService.candidate.unrollRingData = imageData.unrollRingData; + + this.reviewService.candidate.image.src = `${this.config.imageURL}${imageData.ebtsImageId}`; + this.reviewService.candidate.image.uuid = uuid; + + this.reviewService.candidate.ebtsImageId = imageData.ebtsImageId; + + this.reviewService.candidate.image.onload = () => { + this.reviewService.candidate.imgLoaded = true; + this.candidateStatus = ReviewStatus.ACTIVE_CANDIDATE; + + // this needs to be updated AFTER image is loaded, + // so events triggered by this value changing can + // act upon the newly loaded image (as it is already loaded) + this.reviewService.candidate.activeUuid = uuid; + + if (this.loadedUUIDs.indexOf(uuid) < 0) { + this.loadedUUIDs.push(uuid); + } + }; + + if(imageData.recordType == 17 || this.reviewService.currentCandidate.imageList.length == 1){ + if (!this.historyService.hasSavedReview) { + this.reviewService.getEyeLabel(this.reviewService.candidate.ebtsImageId, false); + this.reviewService.getIrisImageQualityAndContact(this.reviewService.candidate.ebtsImageId, false); + } + } + this.reviewService.setComparisonMode(false, this.reviewService.probe, this.reviewService.candidate, this.contentSvc); + } + + onLoadingError(evt) { + alert('Error loading file...'); + console.error(evt); + } + + private _loadProbeImage(imageData: EbtsImageData) { + const uuid = imageData.id; + //same thing as loading candidate image + //so that the image will be updated + + const img = new Image(); + img.crossOrigin = 'anonymous'; // enable CORS + + this.reviewService.probe.unrollImagePath = `${this.config.imageURL}${imageData.unrollImageId}`; + this.reviewService.probe.unrollRingData = imageData.unrollRingData; + + img.src = `${this.config.imageURL}${imageData.ebtsImageId}`; + this.reviewService.probe.ebtsImageId = imageData.ebtsImageId; + + img.onload = (evt) => { + + this.reviewService.probe.image = img; + this.reviewService.probe.imgLoaded = true; + this.reviewService.probe.status = 'loaded'; + this.reviewService.probe.activeUuid = uuid; + + if (this.loadedUUIDs.indexOf(uuid) < 0) { + this.loadedUUIDs.push(uuid); + } + }; + + if(imageData.recordType == 17 || this.reviewService.currentReport.probe.imageList.length == 1) { + // You can use the ebtsImageId since it's not a resubmitted image. + if (!this.historyService.hasSavedReview) { + this.reviewService.getEyeLabel(this.reviewService.probe.ebtsImageId, false); + this.reviewService.getIrisImageQualityAndContact(this.reviewService.probe.ebtsImageId, false); + } + } + + this.reviewService.setComparisonMode(false, this.reviewService.probe, this.reviewService.candidate, this.contentSvc); + + } + + + private getContactType(str: string) { + switch (str) { + case 'None': + case '0': + return ContactType.None; + case 'Cosmetic': + case '1': + return ContactType.Cosmetic; + case 'Clear': + case '2': + return ContactType.Clear; + case '3': + default: + return ContactType['']; + } + } + + setProbeFullResponse(obj: any) { + this.reviewService.currentProbeImageIndex = 0; + this.reviewService.currentReport = obj; + + this.reviewService.currentReport.responseType = obj.responseType; + switch (obj.responseType) { + case 'SRB': + case '1': + this.reviewService.currentReport.responseType = ResponseType.SRB; + break; + case 'ERRB': + case '2': + this.reviewService.currentReport.responseType = ResponseType.ERRB; + break; + case 'XML': + case '3': + this.reviewService.currentReport.responseType = ResponseType.XML; + break; + default: + this.reviewService.currentReport.responseType = ResponseType.Unknown; + break; + } + + if (this.reviewService.currentReport.responseType == ResponseType.ERRB) { + // Show the ERRB error modal, and make sure the errorModalClose function runs if the modal is dismissed + this.modalService.show(this.errorModal); + this.modalService.onHide.subscribe(() => { + this.errorModalClose(); + }); + } else { + this.reviewService.probe.activeUuid = this.reviewService.currentReport.probe.imageList[this.reviewService.currentProbeImageIndex].ebtsImageId; + + if (this.loadedUUIDs.indexOf(this.reviewService.probe.activeUuid) < 0) { + this.loadedUUIDs.push(this.reviewService.probe.activeUuid); + } + + for (const imageData of this.reviewService.currentReport.probe.imageList) { + imageData.eyeLabel = this.reviewService.setEyeLabel(imageData.eyeLabel.toString()); + imageData.contactType = this.getContactType(imageData.contactType.toString()); + } + + for (const candidate of this.reviewService.currentReport.candidates) { + for (const imageData of candidate.imageList) { + imageData.eyeLabel = this.reviewService.setEyeLabel(imageData.eyeLabel.toString()); + imageData.contactType = this.getContactType(imageData.contactType.toString()); + + } + } + + this.candidateStatus = ReviewStatus.FULL_RESPONSE; + this._loadProbeImage(this.reviewService.currentReport.probe.imageList[0]); + } + + } + + get canRunTShepiiCompare(): boolean { + return this.isTshepiiCompareReady; + } + + isDualPdmCompareReady(): boolean { + return (this.reviewService.probe.hasPdmResultsReturned && this.reviewService.candidate.hasPdmResultsReturned); + } + + stopTshepiiResponsePolling() { + this.tshepiiService.stopTshepiiResponseStatusPolling(); + if (this._tshepiiResponseSubscription) { + this._tshepiiResponseSubscription.unsubscribe(); + } + } + + checkTshepiiResponseStatus(): void { + this.stopTshepiiResponsePolling(); + this.isTshepiiCompareReady = false; + if (this.reviewService.probe.ebtsImageId && this.reviewService.candidate.ebtsImageId) { + this.tshepiiService.startTshepiiResponseStatusPolling(this.reviewService.probe.ebtsImageId, this.reviewService.candidate.ebtsImageId, '-1', '-1'); + const serviceStatus = ServiceStatus; + this._tshepiiResponseSubscription = this.tshepiiService.tshepiiResponseStatus$.subscribe( + data => { + switch (data) { + case serviceStatus.Error: { + this.stopTshepiiResponsePolling(); + this.isTshepiiCompareReady = false; + break; + } + case serviceStatus.Received: { + this.stopTshepiiResponsePolling(); + this.isTshepiiCompareReady = true; + break; + } + case serviceStatus.Pending: { + this.isTshepiiCompareReady = false; + break; + } + case serviceStatus.NotSent: { + this.isTshepiiCompareReady = false; + break; + } + default: + console.log('Tshepii: No Status Yet'); + this.isTshepiiCompareReady = false; + this.stopTshepiiResponsePolling(); + break; + } + }); + } + } + + onCandidateLoadSuccess($event) { + this.stopTshepiiResponsePolling(); + const tempObj = JSON.parse($event[0].xhr.response); + + if (tempObj.responseType == undefined) { + + const tempImageDataArray = Object.assign([], tempObj); + const tempCandidate = new Candidate(); + tempImageDataArray.forEach( + (item) =>{ + const tempImageData: EbtsImageData = item; + tempImageData.eyeLabel = EyeLabel['']; + tempImageData.contactType = ContactType['']; + + tempCandidate.imageList.push(tempImageData); + } + ); + + this.reviewService.currentReport.responseType = ResponseType.Image; + this.reviewService.currentReport.candidates.push(tempCandidate); + this.selectCandidate(tempCandidate); + this.canUnloadCandidate = true; + this.singleImageCandidateLoaded = true; + } else { + if (this.canUnloadProbe) { + if (!this.changeProbeImage()) { + window.alert('Single Probe Image already loaded. Please select a single image for Candidate Canvas or unload Probe image'); + return; + } + } + + this.setProbeFullResponse(tempObj); + this.setCanUnloadFile(); + } + this.checkTshepiiResponseStatus(); + this.checkForIdenticalImages(); + } + + onProbeLoadSuccess($event) { + this.stopTshepiiResponsePolling(); + const tempObj = JSON.parse($event[0].xhr.response); + + if (tempObj.responseType == undefined) { + const tempImageDataArray = Object.assign([], tempObj); + + let tabGroup = 0; + let loadImageData; + tempImageDataArray.forEach( + (item) =>{ + const tempImageData: EbtsImageData = item; + tempImageData.eyeLabel = EyeLabel['']; + tempImageData.contactType = ContactType['']; + this.reviewService.currentReport.probe.imageList.push(tempImageData); + + if(!loadImageData || (loadImageData.recordType != 17 && tempImageData.recordType == 17)){ + loadImageData = tempImageData; + this.probeSelectedTab = tabGroup; + } + + tabGroup++; + } + ); + + + this.reviewService.currentProbeImageIndex = this.probeSelectedTab; + + this.reviewService.currentReport.responseType = ResponseType.Image; + this.canUnloadProbe = true; + this.singleImageProbeLoaded = true; + this._loadProbeImage(loadImageData); + + } else { + if (this.canUnloadCandidate) { + if (!this.changeCandidateImage()) { + window.alert('Single Candidate Image already loaded. Please select a single image for Probe Canvas or unload Candidate image'); + return; + } + } + + this.setProbeFullResponse(tempObj); + this.setCanUnloadFile(); + } + + this.checkTshepiiResponseStatus(); + this.checkForIdenticalImages(); + } + + checkSaveImageLoad() { + if (this.singleImageProbeLoaded && this.singleImageCandidateLoaded) { + this.saveReview(true); + } + } + + checkForIdenticalImages() { + const candidates = this.reviewService.currentReport.candidates; + if (candidates.length > 0 && !!this.reviewService.probe) { + candidates.forEach((candidate, candidateIndex) => { + candidate.imageList.forEach((img, imgIndex) => { + if (img.ebtsImageId === this.reviewService.probe.ebtsImageId) { + this.matSnackBar.open(`Image ${imgIndex + 1} for Candidate ${candidateIndex + 1} is identical to the probe.`, 'Dismiss', {duration: -1}); + } + }); + }); + } + } + + onProbeLoad(data) { + this.reviewService.currentReport = data; + //make sure no matter what that the index is set to the first probe image + this.reviewService.currentProbeImageIndex = 0; + + this._loadProbeImage(this.reviewService.currentReport.probe.imageList[0]); + this.canUnloadProbe = true; + + this.setProbeFullResponse(data); + this.setCanUnloadFile(); + } + + setCanUnloadFile() { + this.canUnloadFile = true; + this.canUnloadCandidate = false; + this.canUnloadProbe = false; + } + + onProbeReset($event) { + this.reviewService.probe.status = 'resetting'; + this._loadProbeImage(this.reviewService.currentReport.probe.imageList[0]); + } + + openPreview(previewTemplate: TemplateRef) { + this.previewModalRef = this.modalService.show(previewTemplate); + } + + submitButtonClick(event) { + event.preventDefault(); + this.sendReport(this.reviewService.currentReport); + } + + sendReport(reportData) { + this.previewModalRef.hide(); + this.saveInProgress = true; + this.reviewService.currentReport.allowNullRequester = true; + this.saveReview(false); + + this.responseReviewService.createReport(this.reviewService.currentReport).subscribe( + (ret) => { + this.reviewService.currentReport = ret; + }, + (err) => { + this.saveInProgress = false; + this.matSnackBar.open('Report generation has failed', 'Dismiss', {duration: -1, verticalPosition: 'top'}); + }, + () => { + this.saveInProgress = false; + this.matSnackBar.open('Report sent for generation.', 'Dismiss'); + }); + } + + finalizeReport() { + this.reviewService.currentReport.includeExclude = !this.profileService.getCurrentProfile().isReportExclude; + + for (const candidate of this.reviewService.currentReport.candidates) { + candidate.imageList.forEach(image => { + this.reportImageProcessing(image); + }); + } + + //set the data for the current candidate + if (this.reviewService.currentCandidate != undefined) { + + this.reviewService.currentCandidate.imageList.forEach(image => { + this.reportImageProcessing(image); + }); + } + + //get image data + this.reviewService.currentReport.probe.imageList.forEach(image => { + // I know this looks like duplications, but without doing that, it doesn't work. + // Something is going wrong with redrawing the canvas and setting the image.imageData if the inital + // image.imageData is undefined. + if (image.imageData == undefined) { + this.reportImageProcessing(image); + } + this.reportImageProcessing(image); + }); + + if (this.reviewService.currentReport.fileName == undefined || this.reviewService.currentReport.fileName == '') { + if (this.reviewService.currentReport.probe.transactionControlNumber == undefined + || this.reviewService.currentReport.probe.transactionControlNumber == '') { + this.reviewService.currentReport.fileName = Math.floor(Math.random() * 10000000) + '.pdf'; + } else { + this.reviewService.currentReport.fileName = this.reviewService.currentReport.probe.transactionControlNumber + '.pdf'; + } + } + } + + reportImageProcessing(image: EbtsImageData) { + const reviewSvc = (this.contentSvc); + const canvasPNGData = reviewSvc.getPNGFromCanvasCache(); + const canvasUnrollPNGData = reviewSvc.getUnrollPNGFromCanvasCache(); + const annotationData = reviewSvc.getAnnotationsFromCache(); + const pastStatesData = reviewSvc.getPastStatesFromCache(); + const currStateData = reviewSvc.getCurrStateFromCache(); + const toolManagerCache = reviewSvc.getToolManagerFromCache(); + + const imageToolManager = toolManagerCache[image.id]; + if (imageToolManager != undefined) { + + if (annotationData[image.id]) { + if (this.isSystemGeneratedAnnotationsIncluded) { + image.annotations = annotationData[image.id]; + } else { + imageToolManager.actualAnnotations = []; + image.annotations = []; + annotationData[image.id].forEach(annotation => { + if (!annotation.isSystemGenerated) { + image.annotations.push(annotation); + imageToolManager.actualAnnotations.push(annotation); + } + }); + imageToolManager.redrawAnnotations(); + } + } + + if (imageToolManager.unrollInnerRing != undefined) { + image.unrollRingData = { + innerCenterX: imageToolManager.unrollInnerRing.center.x, + innerCenterY: imageToolManager.unrollInnerRing.center.y, + innerRadius: imageToolManager.unrollInnerRing.radius, + outerCenterX: imageToolManager.unrollOuterRing.center.x, + outerCenterY: imageToolManager.unrollOuterRing.center.y, + outerRadius: imageToolManager.unrollOuterRing.radius, + }; + } + } + + if (currStateData[image.id]) { + if (this.isSystemGeneratedAnnotationsIncluded) { + image.currState = currStateData[image.id]; + } else { + if (!currStateData[image.id].annotation.isSystemGenerated) { + image.currState = currStateData[image.id]; + } + } + } + + if (pastStatesData[image.id]) { + if (this.isSystemGeneratedAnnotationsIncluded) { + image.pastStates = pastStatesData[image.id]; + } else { + image.pastStates = []; + pastStatesData[image.id].forEach(state => { + if (!state.annotation.isSystemGenerated) { + image.pastStates.push(state); + } + }); + } + } + + if (canvasUnrollPNGData[image.id]) { + if (this.isSystemGeneratedAnnotationsIncluded) { + image.unrollImageData = canvasUnrollPNGData[image.id] || image.unrollImageData; + } else { + if (!canvasUnrollPNGData[image.id].isSystemGenerated) { + image.unrollImageData = canvasUnrollPNGData[image.id]; + } + } + } + + if (canvasPNGData[image.id]) { + if (this.isSystemGeneratedAnnotationsIncluded) { + image.imageData = canvasPNGData[image.id] || image.imageData; + } else { + image.imageData = canvasPNGData[image.id]; + } + } + } + + errorModalClose() { + window.location.reload(); + } + + closeSubmit() { + this.reportFieldForm.resetForm(); + this.reviewService.currentReport.probe.transactionControlNumber = ''; + } + + selectProbeImage(index: number) { + this.stopTshepiiResponsePolling(); + //set the current annotations for the current image on the probe + this.reviewService.currentProbeImageIndex = index; + this.isProbeIrisDataMissing = false; + const selectedProbe = this.reviewService.currentReport.probe.imageList[this.reviewService.currentProbeImageIndex]; + this.isProbeIrisDataMissing = this.checkIrisDataMissing(selectedProbe); + + this._loadProbeImage(this.reviewService.currentReport.probe.imageList[this.reviewService.currentProbeImageIndex]); + this.checkTshepiiResponseStatus(); + } + + selectCandidateImage(index: number) { + this.stopTshepiiResponsePolling(); + //get the annotations for the current image and save them + this.reviewService.currentCandidateImageIndex = index; + this.isCandidateIrisDataMissing = false; + const selectedCandidate = this.reviewService.currentCandidate.imageList[this.reviewService.currentCandidateImageIndex]; + this.reviewService.currentCandidate.currentImageIndex = index; + this.isCandidateIrisDataMissing = this.checkIrisDataMissing(selectedCandidate); + + this._loadCandidateImage(this.reviewService.currentCandidate.imageList[this.reviewService.currentCandidateImageIndex]); + this.checkTshepiiResponseStatus(); + } + + showGhostCursor() { + this.isGhostCursorOn = !this.isGhostCursorOn; + const reviewSvc = (this.contentSvc); + const probeInstance = reviewSvc.cache(this.reviewService.probe.activeUuid).instance; + const candidateInstance = reviewSvc.cache(this.reviewService.candidate.activeUuid).instance; + + if (this.isGhostCursorOn) { + probeInstance.toolManager.showGhostCursor = true; + candidateInstance.toolManager.showGhostCursor = true; + } else { + probeInstance.toolManager.showGhostCursor = false; + candidateInstance.toolManager.showGhostCursor = false; + } + } + + saveReview(showSaveSnackBar: boolean) { + if (this.reviewService.currentReport.allowNullRequester != true) { + this.reviewService.currentReport.allowNullRequester = false; + } + + const funct = (report) => { + this.reviewForm.control.markAsPristine(); + this.hasSaveButtonSelected = true; + if (showSaveSnackBar) { + setTimeout(() => { + this.matSnackBar.open('Review saved', 'Dismiss'); + }, 1000); + } + }; + this._saveReview(funct); + } + + completeReview() { + this.reviewService.currentReport.allowNullRequester = true; + const redir = this.redirectUrl; + const funct = (reportId) => { + window.location.assign(redir); + }; + + const handleResponse = (data) => { //data is a Blob object + setInterval(() => { + window.location.reload(); + }, 1000); + }; + + this._saveReview(funct); + this.responseReviewService.createReport(this.reviewService.currentReport) + .subscribe(handleResponse); + } + + _saveReview(funct: any) { + this.saveInProgress = true; + this.finalizeReport(); + this.historyService.saveReview(this.reviewService.currentReport) + .subscribe((reportData) => { + this.reviewService.currentReport = reportData; + this.probeSaved.next(this.reviewService.currentReport.probe.imageList[this.reviewService.currentProbeImageIndex].annotations); + if (this.reviewService.currentCandidate) { + this.candidateSaved.next(this.reviewService.currentCandidate.imageList[this.reviewService.currentCandidateImageIndex].annotations); + } + funct(this.reviewService.currentReport); + + this.saveInProgress = false; + }); + } + + setVertical(vertical) { + this.isVertical = vertical; + this.isVerticalSubject.next(this.isVertical); + } + + /* + * Fired when the newly uploaded image's uuid gets cached into + * ReviewContentService. + * Subscribe to hasUnrolled to toggle Unrolled active tab automatically. + * When the image gets discarded, manually unsubscribe thru statusSubjects + * to prevent leak. + * + */ + updateProbeStatus() { + const reviewSvc = (this.contentSvc); + const probeInstance = reviewSvc.cache(this.reviewService.probe.activeUuid).instance; + + // update the original canva + probeInstance.toolManager.hasOriginal.pipe( + takeUntil(this.probeStatusSubject) + ).subscribe((res: boolean) => { + this.probeShowUnrolledSubject.next(res); + }); + + // update the unrolled canva + probeInstance.toolManager.hasUnrolled.pipe( + takeUntil(this.probeStatusSubject) + ).subscribe((res: boolean) => { + this.probeShowUnrolledSubject.next(res); + }); + + + } + + updateCandidateStatus() { + const reviewSvc = (this.contentSvc); + const candidateInstance = reviewSvc.cache(this.reviewService.candidate.activeUuid).instance; + + // update the original canvas + candidateInstance.toolManager.hasOriginal.pipe( + takeUntil(this.candStatusSubject) + ).subscribe((res: boolean) => { + this.candShowOrigSubject.next(res); + }); + + // update the unrolled canvas + candidateInstance.toolManager.hasUnrolled.pipe( + takeUntil(this.candStatusSubject) + ).subscribe((res: boolean) => { + this.candShowUnrolledSubject.next(res); + }); + } + + checkIrisDataMissing(selectedImage) { + if (selectedImage.dme == 'UC' || selectedImage.dme == 'MA' || selectedImage.dme == null) { + return true; + } else if (selectedImage.ebtsImageId == '-1') { + return true; + } else { + return false; + } + } + +} diff --git a/web/client/src/app/review/review.resolver.ts b/web/client/src/app/review/review.resolver.ts new file mode 100644 index 0000000..8aed4d2 --- /dev/null +++ b/web/client/src/app/review/review.resolver.ts @@ -0,0 +1,33 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; + + +import { ProfileService } from '../profile/profile.service'; +import { HttpClient } from '@angular/common/http'; + +@Injectable() +export class ReviewResolver implements Resolve> { + + configFileUrl = './iris_review_config.json'; + + constructor(private http: HttpClient, private profileService: ProfileService) { + } + + resolve(route: ActivatedRouteSnapshot, + state: RouterStateSnapshot): Promise { + this.profileService.getProfile().subscribe(prfs => { + // Do nothing. simply a session alive check + }); + return this.http.get(this.configFileUrl).toPromise().then(r => r); + } +} diff --git a/web/client/src/app/review/review.service.ts b/web/client/src/app/review/review.service.ts new file mode 100644 index 0000000..e667d76 --- /dev/null +++ b/web/client/src/app/review/review.service.ts @@ -0,0 +1,840 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; + + +import { ContentService } from '../utils/content.service'; +import { HttpClient } from '@angular/common/http'; +import { ServiceStatus } from '../types/service-response'; +import { TshepiiService } from '../tshepii/tshepii.service'; +import { Observable, Subscription } from 'rxjs'; +import { TshepiiCompareResponse } from '../tshepii/tshepii-compare-response'; +import { TshepiiResponse } from '../tshepii/tshepii-response'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { CreateContentService } from '../create/create.service'; +import { ServiceStatusNameType } from '../types/service-status'; +import { Annotation } from '../types/annotation'; +import { ReportData, ResponseType } from '../types/report-data'; +import { Probe } from '../types/probe'; +import { Candidate } from '../types/candidate'; +import { CaseInformation } from '../types/case-information'; +import { ServiceStatusService } from '../service-status/service-status.service'; +import { AciiService } from '../acii/acii.service'; +import { EyeLabel } from '../types/eye-label'; +import { ContactType } from '../types/contact-type'; +import { BiqtContactService } from '../biqt-contact/biqt-contact.service'; +import { DualPDMService } from '../dual-pdm/dual-pdm.service'; + +const SAMPLE_CANDIDATES = + [{determination: 'none', rank: '1', score: 5320, file: 'img1.jpg'}, + {determination: 'none', rank: '2', score: 3210, file: ''}, + {determination: 'none', rank: '3', score: 4320, file: 'img3.jpg'}]; + +@Injectable() +export class ReviewContentService extends ContentService { + configFileUrl = './iris_review_config.json'; + adjudicationUrl = '/api/iris/adjudication'; + colors: any[] = []; + + //STARTING HERE + tshepiiStatusText = ''; + _showTshepiiComparison = false; + _showDualPDMComparison = false; + _tshepiiResponseSubscription: Subscription; + _dualPDMResponseStatusSubscription: Subscription; + _dualPDMStatusSubscription: Subscription; + _aciiResponseSubscription: Subscription; + _biqtResponseSubscription: Subscription; + _contactResponseSubscription: Subscription; + _matchMap = new Map(); + + currentProbeImageIndex = -1; + currentCandidateImageIndex = 0; + currentCandidate: Candidate = undefined; + dualPDMReturnedResults = false; + tshepiiReturnedResults = false; //Set this to true on initialization. We set it to false, once we hit the tshepii mode + + currentReport: ReportData = { + id: '', + fileName: '', + probe: { + id: '', + name: '', + subjectIdentifier: '', + transactionControlReference: '', + transactionControlNumber: '', + imageList: [], + }, + candidates: new Array(), + includeExclude: false, + responseType: ResponseType.Unknown, + caseInformation: new CaseInformation(), + userId: undefined, + isFinished: false, + errorMessages: Array(), + invalidFields: Array(), + workedBy: undefined, + allowNullRequester: false, + }; + + probe = { + showInfo: false, + imgLoaded: false, + ebtsImageId: '', + status: 'empty', + activeUuid: '', + analysisPosition: EyeLabel[''], + image: null, + unrollImagePath: '', + unrollRingData: {}, + hasPdmResultsReturned: false + }; + + candidate = { + showInfo: false, + imgLoaded: false, + ebtsImageId: '', + image: null, + unrollImagePath: '', + resetting: false, + analysisPosition: EyeLabel[''], + activeUuid: '', + unrollRingData: {}, + hasPdmResultsReturned: false + }; + + protected _config: object = { + imageURL: '/api/image/', + url: '/api/iris/response', // Formerly 'upload url', refactored to play nicely with the new dropzone wrapper component + acceptedFiles: '.sub, .ebts, .nist, .eft, .xml, image/png, image/jp2, image/jp2l, image/bmp, image/jpeg, image/jpg, image/tiff, .tiff', + headers: {'X-XSRF-TOKEN': document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*\=\s*([^;]*).*$)|^.*$/, '$1')} + }; + private _irisDisplayCache = {}; + + private _candidateList: any[] = []; + + constructor(private http: HttpClient, + private tshepiiService: TshepiiService, + public matSnackBar: MatSnackBar, + private createService: CreateContentService, + private aciiService: AciiService, + private biqtContactService: BiqtContactService, + private serviceStatusService: ServiceStatusService, + private dualPDMService: DualPDMService) { + super(); + + this._candidateList = SAMPLE_CANDIDATES; + } + + get candidateList() { + return this._candidateList; + } + + cache(key: string, val?: object) { + if (val == undefined) { + return this._irisDisplayCache[key] || null; + } + + this._irisDisplayCache[key] = val; + } + + hasCached(key: string) { + return !!this._irisDisplayCache[key]; + } + + getPNGFromCanvasCache() { + return Object.keys(this._irisDisplayCache).reduce((acc, uuid) => { + const instance = (this._irisDisplayCache[uuid]).instance; + + acc[uuid] = instance.exportFlattenedPNG(); + + return acc; + }, {}); + } + + getUnrollPNGFromCanvasCache() { + return Object.keys(this._irisDisplayCache).reduce((acc, uuid) => { + const instance = (this._irisDisplayCache[uuid]).instance; + acc[uuid] = instance.exportUnrollPNG(); + + return acc; + }, {}); + } + + //returns an object where each key is an image id and the value + //is a list of Annotation values + getAnnotationsFromCache(): object { + const ret = {}; + Object.keys(this._irisDisplayCache).forEach(id => { + const irisDisplay = this._irisDisplayCache[id].instance; + ret[id] = irisDisplay.exportAnnotations(); + }); + + + return ret; + } + + getPastStatesFromCache() { + const ret = {}; + Object.keys(this._irisDisplayCache).forEach(id => { + const irisDisplay = this._irisDisplayCache[id].instance; + ret[id] = irisDisplay.exportPastStates(); + }); + + return ret; + } + + getCurrStateFromCache() { + const ret = {}; + Object.keys(this._irisDisplayCache).forEach(id => { + const irisDisplay = this._irisDisplayCache[id].instance; + ret[id] = irisDisplay.exportCurrState(); + }); + + return ret; + } + + getToolManagerFromCache(): object { + const ret = {}; + Object.keys(this._irisDisplayCache).forEach(id => { + const irisDisplay = this._irisDisplayCache[id].instance; + ret[id] = irisDisplay.toolManager; + }); + + return ret; + } + + setDualPDMComparisonMode(comparisonMode: boolean, probe: any, candidate: any, contentSvc: ContentService) { + this._showDualPDMComparison = comparisonMode; + const reviewSvc = (contentSvc); + const probeInstance = reviewSvc.cache(probe.activeUuid).instance; + const candidateInstance = reviewSvc.cache(candidate.activeUuid).instance; + + probeInstance.toggleComparison(!comparisonMode, !comparisonMode, !comparisonMode, comparisonMode); + candidateInstance.toggleComparison(!comparisonMode, !comparisonMode, !comparisonMode, comparisonMode); + probeInstance.toggleToolBar(this._showDualPDMComparison, true); + candidateInstance.toggleToolBar(this._showDualPDMComparison, true); + + } + + getDualPDM(probeEbtsImageId: string, candidateEbtsImageId: string, isResubmit: boolean, probeModifiedImageId: string, candidateModifiedImageId: string) { + if (this.serviceStatusService.isServiceDisabled(ServiceStatusNameType.dualpdm)) { + return; + } + + this.checkDualPDMIndividualStatus(probeEbtsImageId, candidateEbtsImageId, isResubmit, probeModifiedImageId, candidateModifiedImageId); + } + + checkDualPDMIndividualStatus(probeEbtsImageId: string, candidateEbtsImageId: string, isResubmit: boolean, probeModifiedImageId?: string, candidateModifiedImageId?: string) { + + let probeId = probeEbtsImageId; + if (!!probeModifiedImageId) { + probeId = probeModifiedImageId; + } else { + probeModifiedImageId = '-1'; + } + + let candidateId = candidateEbtsImageId; + if (!!candidateModifiedImageId) { + candidateId = candidateModifiedImageId; + } else { + candidateModifiedImageId = '-1'; + } + + this.dualPDMService.startDualPdmResponseStatusPolling(probeEbtsImageId, candidateEbtsImageId, probeModifiedImageId, candidateModifiedImageId); + const serviceStatus = ServiceStatus; + + this._dualPDMStatusSubscription = this.dualPDMService.dualpdmResponseStatus$.subscribe( + data => { + switch (data) { + // Error + case serviceStatus.Error: { + this.dualPDMService.stopDualPDMResponseStatusPolling(); + this._dualPDMStatusSubscription.unsubscribe(); + break; + } + + // Received + case serviceStatus.Received: { + this.dualPDMService.stopDualPDMResponseStatusPolling(); + this.getDualPDMReponses(probeEbtsImageId, candidateEbtsImageId, isResubmit, probeModifiedImageId, candidateModifiedImageId); + this._dualPDMStatusSubscription.unsubscribe(); + break; + } + + // Pending + case serviceStatus.Pending: { + break; + } + + // Not Sent + case serviceStatus.NotSent: { + break; + } + + // Default + default: + this.dualPDMService.stopDualPDMResponseStatusPolling(); + break; + } + }); + } + + getDualPDMReponses(probeEbtsImageId: string, candidateEbtsImageId: string, isResubmit: boolean, probeModifiedImageId?: string, candidateModifiedImageId?: string) { + + let probeId = probeEbtsImageId; + if (!!probeModifiedImageId && probeModifiedImageId != '-1') { + probeId = probeModifiedImageId; + } else { + probeModifiedImageId = '-1'; + } + + let candidateId = candidateEbtsImageId; + if (!!candidateModifiedImageId && candidateModifiedImageId != '-1') { + candidateId = candidateModifiedImageId; + } else { + candidateModifiedImageId = '-1'; + } + + this.dualPDMService.startDualPDMCompareStatusPolling(probeEbtsImageId, candidateEbtsImageId, probeModifiedImageId, candidateModifiedImageId); + const serviceStatus = ServiceStatus; + + this._dualPDMResponseStatusSubscription = this.dualPDMService.dualPDMCompareStatus$.subscribe( + data => { + switch (data) { + case serviceStatus.Error: { + this.dualPDMService.stopDualPDMCompareStatusPolling(); + this._dualPDMResponseStatusSubscription.unsubscribe(); + break; + } + + // Received + case serviceStatus.Received: { + this.dualPDMService.stopDualPDMCompareStatusPolling(); + this.dualPDMService.getDualPDMComparison(probeId, candidateId, isResubmit).subscribe( + response => { + this.dualPDMReturnedResults = true; + }); + this._dualPDMResponseStatusSubscription.unsubscribe(); + break; + } + + // Pending + case serviceStatus.Pending: { + break; + } + + // Not Sent + case serviceStatus.NotSent: { + break; + } + + // Default + default: + this.dualPDMService.stopDualPDMCompareStatusPolling(); + break; + } + }); + } + + + + getTshepii(probe: any, candidate: any, contentSvc: ContentService, isResubmit: boolean, annotations: object, modifiedProbeId?: string, modifiedCandidateId?: string) { + if (this.serviceStatusService.isServiceDisabled(ServiceStatusNameType.tshepii)) { + return; + } + + // set the tshepiiReturnedResults to false. + this.tshepiiReturnedResults = false; + this.tshepiiStatusText = 'fetching TSHEPII annotations...'; + + // Before we get the Tshepii Comparison Response, we need to check to make sure that the Tshepii Responses for both images came back as Received. + if (this._showTshepiiComparison) { + this.checkTshepiiResponseStatusAndGetComparison(contentSvc, isResubmit, annotations, probe, candidate, modifiedProbeId, modifiedCandidateId); + } + } + + checkTshepiiResponseStatusAndGetComparison(contentSvc: ContentService, isResubmit: boolean, annotations: object, probe: any, candidate: any, probeModifiedImageId?: string, candidateModifiedImageId?: string) { + // Please note that this method and the getTshepiiComparisonResults method looks the same, but this one is subscribing to the TshepiiResponseStatus, where as the other method is subscribing to TshepiiCompareStatus!! + let probeId = probe.ebtsImageId; + let candidateId = candidate.ebtsImageId; + if (!!probeModifiedImageId) { + probeId = probeModifiedImageId; + } else { + probeModifiedImageId = '-1'; + } + if (!!candidateModifiedImageId) { + candidateId = candidateModifiedImageId; + } else { + candidateModifiedImageId = '-1'; + } + + this.tshepiiService.startTshepiiResponseStatusPolling(probe.ebtsImageId, candidate.ebtsImageId, probeModifiedImageId, candidateModifiedImageId); + const serviceStatus = ServiceStatus; + this._tshepiiResponseSubscription = this.tshepiiService.tshepiiResponseStatus$.subscribe( + data => { + switch (data) { + case serviceStatus.Error: { + this.tshepiiService.stopTshepiiResponseStatusPolling(); + this._tshepiiResponseSubscription.unsubscribe(); + break; + } + // If the status is receieved for both images, we can submit the images to get the Tshepii Comparsion Results + case serviceStatus.Received: { + this.tshepiiService.stopTshepiiResponseStatusPolling(); + this._tshepiiResponseSubscription.unsubscribe(); + this.tshepiiService.getTshepiiComparison(probeId, candidateId, isResubmit).subscribe( + response => { + } + ); + // Now we can pull the Tshepii Comparison Results from the database + this.getTshepiiComparisonResults(probeModifiedImageId, candidateModifiedImageId, contentSvc, isResubmit, annotations, probe, candidate); + break; + } + case serviceStatus.Pending: { + break; + } + case serviceStatus.NotSent: { + break; + } + default: + console.log('Tshepii: No Status Yet'); + this.tshepiiService.stopTshepiiResponseStatusPolling(); + break; + } + }); + } + + getTshepiiComparisonResults(probeModifiedImageId: string, candidateModifiedImageId: string, contentSvc: ContentService, isResubmit: boolean, annotations: object, probe: any, candidate: any) { + // Again, please note that this method looks similar to checkTshepiiResponseStatusAndGetComparison method, but this method is subscribing to the TshepiiCompareStatus not TshepiiResponseStatus!! + let probeId = probe.ebtsImageId; + let candidateId = candidate.ebtsImageId; + + if (!!probeModifiedImageId && probeModifiedImageId !== '-1') { + probeId = probeModifiedImageId; + } + if (!!candidateModifiedImageId && candidateModifiedImageId !== '-1') { + candidateId = candidateModifiedImageId; + } + + this.tshepiiService.startTshepiiCompareStatusPolling(probe.ebtsImageId, candidate.ebtsImageId, probeModifiedImageId, candidateModifiedImageId); + const serviceStatus = ServiceStatus; + let tshepiiCompareStatusCounter = 0; + let subscriptionId = probeId + "+" + candidateId; + const pendingTimer = this.createService.setPendingTimer(isResubmit, ServiceStatusNameType.tshepii); + this.tshepiiService._tshepiiCompareSubscription[subscriptionId] = this.tshepiiService.tshepiiCompareStatus$.subscribe( + data => { + this.createService.clearPendingTimer(data, pendingTimer); + switch (data) { + case serviceStatus.Error: { + this.tshepiiService.stopTshepiiCompareStatusPolling(subscriptionId); + this.tshepiiStatusText = 'Error fetching TSHEPII data.'; + this.setComparisonMode(!this._showTshepiiComparison, probe, candidate, contentSvc); + break; + } + // If the status of the Tshepii Comparison is received, we can post the tshepii responses + case serviceStatus.Received: { + this.tshepiiService.getTshepiiComparison(probeId, candidateId, isResubmit).subscribe( + tshepiiCompareResponse => { + this.tshepiiReturnedResults = true; + tshepiiCompareResponse.isResubmit = isResubmit; + this.tshepiiService.stopTshepiiCompareStatusPolling(subscriptionId); + this.checkOverlap(tshepiiCompareResponse, probe, candidate, contentSvc, annotations); + this.tshepiiStatusText = ''; + this.createService.showSnackBar(isResubmit, false, ServiceStatusNameType.tshepii); + }); + break; + } + case serviceStatus.Pending: { + if (tshepiiCompareStatusCounter > 3) { + this.tshepiiStatusText = 'Waiting on TSHEPII service to return....'; + } else { + tshepiiCompareStatusCounter++; + } + break; + } + case serviceStatus.NotSent: { + if (tshepiiCompareStatusCounter > 3) { + this.setComparisonMode(!this._showTshepiiComparison, probe, candidate, contentSvc); + this.tshepiiService.stopTshepiiCompareStatusPolling(subscriptionId); + this.createService.showSnackBar(false, false, ServiceStatusNameType.tshepii, 'TSHEPII Service Unreachable. Please try again'); + } else { + tshepiiCompareStatusCounter++; + } + break; + } + default: + console.log('Tshepii: No Status Yet'); + this.setComparisonMode(!this._showTshepiiComparison, probe, candidate, contentSvc); + this.tshepiiService.stopTshepiiCompareStatusPolling(subscriptionId); + break; + } + }); + } + + setComparisonMode(comparisonMode: boolean, probe: any, candidate: any, contentSvc: ContentService) { + this._showTshepiiComparison = comparisonMode; + try { + const reviewSvc = (contentSvc); + const probeInstance = reviewSvc.cache(probe.activeUuid).instance; + const candidateInstance = reviewSvc.cache(candidate.activeUuid).instance; + + probeInstance.toggleComparison(!comparisonMode, !comparisonMode, !comparisonMode, comparisonMode); + candidateInstance.toggleComparison(!comparisonMode, !comparisonMode, !comparisonMode, comparisonMode); + probeInstance.toggleToolBar(this._showTshepiiComparison); + candidateInstance.toggleToolBar(this._showTshepiiComparison); + } catch (e) { + } + } + + checkOverlap(tshepiiCompareResponse: TshepiiCompareResponse, probe: any, candidate: any, contentSvc: ContentService, annotations: object) { + const reviewSvc = (contentSvc); + const probeInstance = reviewSvc.cache(probe.activeUuid).instance; + const candidateInstance = reviewSvc.cache(candidate.activeUuid).instance; + + if (this._showTshepiiComparison) { + let probeInitResponse: TshepiiResponse; + let candidateInitResponse: TshepiiResponse; + + if (probeInstance._tshepiiResponse) { + probeInitResponse = probeInstance._tshepiiResponse; + } + + if (candidateInstance._tshepiiResponse) { + candidateInitResponse = candidateInstance._tshepiiResponse; + } + + const probeResponse = tshepiiCompareResponse.probeResponse; + const candidateResponse = tshepiiCompareResponse.candidateResponse; + + if (tshepiiCompareResponse.error) { + window.alert('TSHEPII Response error'); + this.setComparisonMode(!this._showTshepiiComparison, probe, candidate, contentSvc); + return; + } + + if (probeResponse.cropRadius == 0 || candidateResponse.cropRadius == 0) { + window.alert('TSHEPII Response error'); + this.setComparisonMode(!this._showTshepiiComparison, probe, candidate, contentSvc); + return; + } + + if (!this.checkAnnotationsForTshepii(Array.from(annotations[probe.activeUuid]))) { + probeInstance.setTshepii(probeResponse, '#00ff00'); + } + + if (!this.checkAnnotationsForTshepii(Array.from(annotations[candidate.activeUuid]))) { + candidateInstance.setTshepii(candidateResponse, '#ff0000'); + } + + if (probeResponse.matchIndex.length != candidateResponse.matchIndex.length) { + console.log('Match Index Lengths do not match'); + } else { + const newKey = probe.activeUuid + candidate.activeUuid; + const tempArray = []; + + for (let i = 0; i < probeResponse.matchIndex.length; i++) { + const match = []; + match.push(probeResponse.matchIndex[i]); + match.push(candidateResponse.matchIndex[i]); + + tempArray.push(match); + } + + this._matchMap.set(newKey, tempArray); + this.drawTshepiiComparison(probe, candidate, contentSvc); + } + } + } + + checkAnnotationsForTshepii(annotations: any): boolean { + let returnValue = false; + + annotations.some((anno: Annotation) => { + if (anno.text === "Tshepii") { + returnValue = true; + } else { + if (anno.tempChildren.length > 0) { + anno.tempChildren.forEach(child => { + if (child.tempChildren.length > 0 && child.text !== "Tshepii") { + returnValue = this.checkAnnotationsForTshepii(child); + } else { + if (child.text === "Tshepii") { + returnValue = true; + } else { + returnValue = false; + } + } + }); + } else { + returnValue = false; + } + } + }); + return returnValue; + } + + drawTshepiiComparison(probe: any, candidate: any, contentSvc: ContentService) { + const reviewSvc = (contentSvc); + const probeInstance = reviewSvc.cache(probe.activeUuid).instance; + const candidateInstance = reviewSvc.cache(candidate.activeUuid).instance; + + probeInstance.toolManager.removeTshepiiItems(); + probeInstance.toolManager.removeTshepiiHighlights(); + + candidateInstance.toolManager.removeTshepiiItems(); + candidateInstance.toolManager.removeTshepiiHighlights(); + + const keyCheck = probe.activeUuid + candidate.activeUuid; + + const temp = this._matchMap.get(keyCheck); + + for (let i = 0; i < temp.length; i++) { + if (temp.length >= i) { + const tempArray = temp[i]; + const probeIndex = tempArray[0]; + const candidateIndex = tempArray[1]; + + const probeAnno = probeInstance.tshepiiAnnotations[probeIndex]; + const candAnno = candidateInstance.tshepiiAnnotations[candidateIndex]; + + if (probeAnno != undefined) { + const probePath = probeInstance.toolManager.addTshepiiPoly(probeAnno, '#00ff00'); + probePath.matchIndex = probeIndex; + + /********************** SAME COLOR - Mouse Over CODE ****************************/ + + probePath.onMouseEnter = function(event) { + this.fillColor = '#ff0000'; + this.fillColor.alpha = .2; + this.bringToFront(); + + for (const subArray of temp) { + const subProbeIndex = subArray[0]; + if (this.matchIndex == subProbeIndex) { + const subCandidateIndex = subArray[1]; + const candSubAnno = candidateInstance.tshepiiAnnotations[subCandidateIndex]; + candidateInstance.toolManager.addTshepiiHighlight(candSubAnno, '#00ff00'); + } + } + }; + + probePath.onMouseLeave = function(event) { + this.fillColor = '#00ff00'; + this.fillColor.alpha = .1; + candidateInstance.toolManager.removeTshepiiHighlights(); + }; + + /******************* END SAME COLOR - Mouse Over CODE ****************************/ + } + if (candAnno != undefined) { + const candidatePath = candidateInstance.toolManager.addTshepiiPoly(candAnno, '#ff0000'); + candidatePath.matchIndex = candidateIndex; + + /********************** SAME COLOR - Mouse Over CODE ****************************/ + + candidatePath.onMouseEnter = function(event) { + this.fillColor = '#00ff00'; + this.fillColor.alpha = .2; + this.bringToFront(); + + for (const subArray of temp) { + const subCandidateIndex = subArray[1]; + if (this.matchIndex == subCandidateIndex) { + const subProbeIndex = subArray[0]; + const probeSubAnno = probeInstance.tshepiiAnnotations[subProbeIndex]; + probeInstance.toolManager.addTshepiiHighlight(probeSubAnno, '#ff0000'); + } + } + }; + + candidatePath.onMouseLeave = function(event) { + this.fillColor = '#ff0000'; + this.fillColor.alpha = .1; + probeInstance.toolManager.removeTshepiiHighlights(); + }; + + /******************* END SAME COLOR - Mouse Over CODE ****************************/ + } + } + } + } + + getAdjudicationOptions(): Observable { + return this.http.get(this.adjudicationUrl); + } + + getIrisImageQualityAndContact(ebtsImageId: string, isResubmit: boolean, modifiedImageId?: string): void { + if (this.serviceStatusService.isServiceDisabled(ServiceStatusNameType.biqt) || this.serviceStatusService.isServiceDisabled(ServiceStatusNameType.contactdetection)) { + return; + } + + this.biqtContactService.requestBiqtContactResponse(ebtsImageId, isResubmit, modifiedImageId); + + const biqtPendingTimer = this.createService.setPendingTimer(isResubmit, ServiceStatusNameType.biqt); + this._biqtResponseSubscription = this.biqtContactService.biqtResponse$.subscribe( + response => { + + this.createService.clearPendingTimer(response.status, biqtPendingTimer); + const quality = response.quality; + + // If it NOT a resubmit, the biqtReponse.imageId should be the same number + // as the current image's ebtsImageId. However if it IS a resubmit, + // the new modified image will have a different Id, so we need to base it off + // the original Id number. + if (!response.isResubmit) { + // There is a small chance that both images + // may have same Hash/EBTS Image Id. So we need to loop through both sets + // of images and update all, not just probe OR candidate. + this.checkBiqtImageId(response.imageId, quality); + } else { + this.checkBiqtImageId(response.ebtsImageId, quality); + } + + this._biqtResponseSubscription.unsubscribe(); + this.createService.showSnackBar(response.isResubmit, false, ServiceStatusNameType.biqt); + }); + + + const contactPendingTimer = this.createService.setPendingTimer(isResubmit, ServiceStatusNameType.contactdetection); + this._contactResponseSubscription = this.biqtContactService.contactResponse$.subscribe( + response => { + let contactType; + this.createService.clearPendingTimer(response.status, contactPendingTimer); + switch (response.detectionCode) { + case "0": + contactType = ContactType.None; + break; + case "1": + contactType = ContactType.Cosmetic; + break; + case "2": + contactType = ContactType.Clear; + break; + case "3": + default: + contactType = ContactType['']; + break; + } + + // If it NOT a resubmit, the contactResponse.imageId should be the same number + // as the current image's ebtsImageId. However if it IS a resubmit, + // the new modified image will have a different Id, so we need to base it off + // the original Id number. + if (!isResubmit) { + // There is a small chance that both images + // may have same Hash/EBTS Image Id. So we need to loop through both sets + // of images and update all, not just probe OR candidate. + this.checkContactImageId(response.imageId, contactType); + } else { + this.checkContactImageId(response.ebtsImageId, contactType); + } + + this._contactResponseSubscription.unsubscribe(); + this.createService.showSnackBar(isResubmit, false, ServiceStatusNameType.contactdetection); + }); + + } + + checkBiqtImageId(imageId: string, quality: number): void { + if (this.currentProbeImageIndex != -1) { + if (this.currentReport.probe.imageList[this.currentProbeImageIndex].ebtsImageId == imageId) { + this.currentReport.probe.imageList[this.currentProbeImageIndex].qualityScore = quality; + } + } + + if (this.currentCandidate) { + if (this.currentCandidate.imageList[this.currentCandidateImageIndex].ebtsImageId == imageId) { + this.currentCandidate.imageList[this.currentCandidateImageIndex].qualityScore = quality; + } + } + } + + checkContactImageId(imageId: string, contactType: ContactType): void { + if (this.currentProbeImageIndex != -1) { + if (this.currentReport.probe.imageList[this.currentProbeImageIndex].ebtsImageId == imageId) { + this.currentReport.probe.imageList[this.currentProbeImageIndex].contactType = contactType; + } + } + + if (this.currentCandidate) { + if (this.currentCandidate.imageList[this.currentCandidateImageIndex].ebtsImageId == imageId) { + this.currentCandidate.imageList[this.currentCandidateImageIndex].contactType = contactType; + } + } + } + + getEyeLabel(ebtsImageId: string, isResubmit: boolean, modifiedImageId?: string): void { + if (this.serviceStatusService.isServiceDisabled(ServiceStatusNameType.acii)) { + return; + } + + this.aciiService.requestAciiResponse(ebtsImageId, isResubmit, modifiedImageId); + + const pendingTimer = this.createService.setPendingTimer(isResubmit, ServiceStatusNameType.acii); + this._aciiResponseSubscription = this.aciiService.aciiResponse$.subscribe(aciiResponse => { + this.createService.clearPendingTimer(aciiResponse.status, pendingTimer); + const orientation = aciiResponse.horizontal; + + // If it is NOT a resubmit, the aciiResponse.imageId should be the same number as the current image's ebtsImageId. + // However, if it IS a resubmit, the new modified image will have a different id, so we need to base it off + // the original Id number. + if (!aciiResponse.isResubmit) { + // There is a small chance that both images may have the same Hash/EBTS Image Id. So, we need to loop through + // both sets of images and update all, not just the probe or candidate. + this.checkAciiImageId(aciiResponse.imageId, orientation); + } else { + this.checkAciiImageId(aciiResponse.ebtsImageId, orientation); + } + + this._aciiResponseSubscription.unsubscribe(); + this.createService.showSnackBar(aciiResponse.isResubmit, false, ServiceStatusNameType.acii); + }); + } + + checkAciiImageId(imageId: string, orientation: string): void { + if (this.currentProbeImageIndex != -1) { + let currentProbeImage = this.currentReport.probe.imageList[this.currentProbeImageIndex]; + if (currentProbeImage.ebtsImageId == imageId){ + currentProbeImage.eyeLabel = this.setEyeLabel(orientation); + this.probe.analysisPosition = this.setEyeLabel(orientation); + } + } + + if (this.currentCandidate) { + if (this.currentCandidate.imageList[this.currentCandidateImageIndex].ebtsImageId == imageId) { + this.currentCandidate.imageList[this.currentCandidateImageIndex].eyeLabel = this.setEyeLabel(orientation); + this.candidate.analysisPosition = this.setEyeLabel(orientation); + } + } + } + + //this is because in typescript enums can be different types + //and they can be different types coming from a json response + public setEyeLabel(str: string) { + switch (str) { + case 'Undefined': + case '0': + return EyeLabel.Undefined; + case 'Right': + case '1': + return EyeLabel.Right; + case 'Left': + case '2': + return EyeLabel.Left; + case '': + case '3': + default: + return EyeLabel['']; + } + } + +} diff --git a/web/client/src/app/review/unroll-compare/unroll-compare-dialog.component.css b/web/client/src/app/review/unroll-compare/unroll-compare-dialog.component.css new file mode 100644 index 0000000..6b4f0bf --- /dev/null +++ b/web/client/src/app/review/unroll-compare/unroll-compare-dialog.component.css @@ -0,0 +1,89 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +.dialog-header { + display: flex; + width: 100%; +} + +.horizontal-fill { + flex: auto; +} + +.dialog-body { + overflow-y: auto; +} + +.pull-left { + float: left; +} + +.flex { + display: flex; +} + +.options-table { + width: 75%; + margin: 1em auto; +} + +.option { + height: 50px; + width: 25px; +} + +.option-space { + height: 50px; + width: 10px; +} + +.red { + background-color: #ff0000; +} + +.green { + background-color: #00ff00; +} + +.blue { + background-color: #0000ff; +} + +.yellow { + background-color: #ffff00; +} + +.gray { + background-color: #cccccc; +} + + +div.unroll-container { + flex: 1; + display: inline-block; +} + +div.overlay-button-container { + display: grid; +} + +div.overlay-canvas-container { + border-style: ridge; +} + +div.overlay-slider-container { + display: flex; +} + +#overlay-tools { + display: flex; + justify-content: center; + align-items: center; +} diff --git a/web/client/src/app/review/unroll-compare/unroll-compare-dialog.component.html b/web/client/src/app/review/unroll-compare/unroll-compare-dialog.component.html new file mode 100644 index 0000000..49cda41 --- /dev/null +++ b/web/client/src/app/review/unroll-compare/unroll-compare-dialog.component.html @@ -0,0 +1,133 @@ + +
+
  +

Comparison

+
+ + +
+
+ +

Images have been colorized to help contrast differences in the overlay pane

+ + +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + +

Color Contrast Options

Red/Green

None

Blue/Yellow

+
+ Indicate Nearest Point - Select Precision: +
+
+
+
+ +
+
+

Probe

+
+ +
+

+
+
+

Candidate

+
+ +
+

+
+
+ +
+

Overlay

+
+ + +
+
+
+ + + +
+
+ +
+
+
+ + + +
+
+
+
+
+
diff --git a/web/client/src/app/review/unroll-compare/unroll-compare-dialog.component.ts b/web/client/src/app/review/unroll-compare/unroll-compare-dialog.component.ts new file mode 100644 index 0000000..1cb03b9 --- /dev/null +++ b/web/client/src/app/review/unroll-compare/unroll-compare-dialog.component.ts @@ -0,0 +1,644 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component, OnInit, OnDestroy, AfterContentInit, Inject } from '@angular/core'; +import { Paper } from '../../utils/paper.service'; +import { Project, Raster, Path, Group, Point, Rectangle, Layer } from 'paper'; +import { Annotation, AnnotationType } from '../../types/annotation'; +import { ClosestPair } from '../../tshepii/closest-pair'; +import Color = paper.Color; +import { MatDialogRef } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { LinkedMap } from '../../types/linked-map'; + +interface UnrollDisplay { + width: number; + height: number; + project: InstanceType; + raster: InstanceType; + loading: boolean; + offset: number; + canvas: HTMLCanvasElement; +} + +declare let jQuery: any; + +const DEFAULT_UNROLL_DISPLAY = { + width: 0, + height: 0, + project: null, + raster: null, + loading: true, + canvas: null, + offset: 0 +}; + +@Component({ + selector: 'app-unroll-compare-dialog', + templateUrl: './unroll-compare-dialog.component.html', + styleUrls: ['./unroll-compare-dialog.component.css'], + +}) +export class UnrollCompareDialogComponent implements OnInit, OnDestroy, AfterContentInit { + public probe: UnrollDisplay = Object.assign({}, DEFAULT_UNROLL_DISPLAY); + public candidate: UnrollDisplay = Object.assign({}, DEFAULT_UNROLL_DISPLAY); + public overlay: UnrollDisplay = Object.assign({}, DEFAULT_UNROLL_DISPLAY); + + private probeAnnotations = []; + private candidateAnnotations = []; + + private probePointAnnotations = []; + private candidatePointAnnotations = []; + + private probeLayer: InstanceType; + private candidateLayer: InstanceType; + + previousOverlayValue = 1; + _showClosePoints = 10; + + private inView = false; + showOverlayOptions = false; + private probeHeightOffset = 0; + private candidateHeightOffset = 0; + + private divAdjustX = 40; + private divAdjustY = 40; + + _showProbeAnnotations = true; + _showCandidateAnnotations = true; + + slider = { + value: 0.5 + }; + + constructor(public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public _input: any, + private paperSvc: Paper) { + } + + ngOnInit() { + // Big change to calculate points at beginning, and reference those points later. + // Old way recalculated every reset + this.walkLoadPoints(this.probePointAnnotations, this._input.probe.toolManager.annotations); + this.walkLoadPoints(this.candidatePointAnnotations, this._input.candidate.toolManager.annotations); + this.resetCanvas(); + } + + walkLoadPoints(pointAnnotations: any[], annotations: LinkedMap) { + for (const annotation of annotations) { + if (annotation.type == AnnotationType.POINT) { + pointAnnotations.push(annotation); + } else if (annotation.type == AnnotationType.GROUP) { + this.walkLoadPoints(pointAnnotations, annotation.children); + } + } + } + + resetCanvas() { + const probe = this._input.probe.toolManager.unrolledRaster; + const candidate = this._input.candidate.toolManager.unrolledRaster; + + this.probe.raster = probe.clone({insert: false}); + this.probe.width = probe.bounds.width + 30; + this.probe.height = probe.bounds.height + 30; + + this.candidate.raster = candidate.clone({insert: false}); + this.candidate.width = candidate.bounds.width + 30; + this.candidate.height = candidate.bounds.height + 30; + + this.overlay.width = Math.max(this.probe.width, this.candidate.width) + 10; + this.overlay.height = Math.max(this.probe.height, this.candidate.height) + 10; + this.overlay.width *= 2; + this.overlay.height *= 2; + + for (const annotation of this.probePointAnnotations) { + const newPt = this._input.probe.toolManager.mainToUnroll(new Point(annotation.value.points[0].x, annotation.value.points[0].y)); + + if (newPt.x == -1 || newPt.y == -1) { +continue; +} + + //adjust for the 15 px boundary on the unroll compare modal + newPt.x += 15 + this.probe.offset; + newPt.y += 15 + this.probe.offset; + + const circle = new Path.Circle(newPt, annotation.value.data['radius']); + circle.fillColor = annotation.value.data['fillColor']; + circle.strokeWidth = annotation.value.data['strokeWidth']; + circle.strokeColor = annotation.value.data['strokeColor']; + this.probeAnnotations.push(circle); + } + + this.probeLayer = new Layer({name: 'unroll-annotations'}); + const probeGroup = this.probeLayer.addChild(new Group()); + + for (const path of this.probeAnnotations) { + probeGroup.addChild(path); + } + + for (const annotation of this.candidatePointAnnotations) { + const newPt = this._input.candidate.toolManager.mainToUnroll(new Point(annotation.value.points[0].x, annotation.value.points[0].y)); + + if (newPt.x == -1 || newPt.y == -1) { +continue; +} + + //adjust for the 15 px boundary on the unroll compare modal + newPt.x += 15 + this.candidate.offset; + newPt.y += 15 + this.candidate.offset; + + const circle = new Path.Circle(newPt, annotation.value.data['radius']); + circle.fillColor = annotation.value.data['fillColor']; + circle.strokeWidth = annotation.value.data['strokeWidth']; + circle.strokeColor = annotation.value.data['strokeColor']; + this.candidateAnnotations.push(circle); + } + + this.candidateLayer = new Layer({name: 'unroll-annotations'}); + const candidateGroup = this.candidateLayer.addChild(new Group()); + + for (const path of this.candidateAnnotations) { + candidateGroup.addChild(path); + } + } + + ngOnDestroy() { + } + + ngAfterContentInit() { + this.inView = true; + setTimeout(() => this.drawCompareItems([1.25, 0.75, 0.75], [0.75, 1.25, 0.75]), 500); + } + + onSliderChange() { + this.overlay.project.activeLayer.children['probe'].opacity = 1 - this.slider.value; + this.overlay.project.activeLayer.children['candidate'].opacity = this.slider.value; + } + + private static tintRaster(raster: InstanceType, channel) { + for (let y = 0; y < raster.height; y++) { + for (let x = 0; x < raster.width; x++) { + const color = raster.getPixel(x, y); + color.red *= channel[0]; + color.green *= channel[1]; + color.blue *= channel[2]; + raster.setPixel(x, y, color); + } + } + }; + + static drawSliderControls(ud: UnrollDisplay, drawFn: () => void) { + const project: InstanceType = ud.project; + + project.activate(); + + const SLIDER_ICON_RADIUS = 8; + const bounds = project.activeLayer.firstChild.bounds.clone(); + const ctrlsGroup = project.activeLayer.addChild(new Group()); + + // top slider control + const topPoint = new Point(project.view.center.x, bounds.y - 3); + const botPoint = new Point(project.view.center.x, bounds.y + bounds.height + 5); + + const top = ctrlsGroup.addChild(new Path.RegularPolygon({ + center: topPoint.clone(), + sides: 3, + radius: SLIDER_ICON_RADIUS, + fillColor: 'black' + })); + top.rotate(180); + + // bottom slider control + const bottom = ctrlsGroup.addChild(new Path.RegularPolygon({ + center: botPoint.clone(), + sides: 3, + radius: SLIDER_ICON_RADIUS, + fillColor: 'black' + })); + + const centerLine = ctrlsGroup.addChild(new Path.Line({ + from: topPoint.clone(), + to: botPoint.clone(), + strokeWidth: 3, + strokeColor: 'black', + visible: false + })); + + let isMouseDown = false; + let isUpdating = false; + ctrlsGroup.on({ + mouseenter: evt => { + project.view.element.classList.add('draggable'); + centerLine.visible = true; + }, + mouseleave: evt => { + if (!isMouseDown) { + project.view.element.classList.remove('draggable'); + project.view.element.classList.remove('dragging'); + } + centerLine.visible = false; + }, + mousedown: evt => { + project.view.element.classList.remove('draggable'); + project.view.element.classList.add('dragging'); + isMouseDown = true; + }, + mouseup: evt => { + project.view.element.classList.add('draggable'); + project.view.element.classList.remove('dragging'); + isMouseDown = false; + centerLine.visible = false; + }, + mousedrag: evt => { + // ignore events in which the x doesn't "move" + if (isUpdating || evt.delta.x === 0) { + return false; + } + + const position = ctrlsGroup.position.x + evt.delta.x; + + centerLine.visible = true; + + if (position < 15 || position > 375) { + return; + } + + isUpdating = true; + + ctrlsGroup.position.x += evt.delta.x; + + const raster: InstanceType = ud.raster; + let main: InstanceType; + let sub: InstanceType; + + // left is negative, right is positive + + if (evt.delta.x < 0) { + // shifting to the left + + // NOTE: This rect is from the perspective of the Raster (not the overall view/canvas) + const mainRect = new Rectangle({ + point: new Point(Math.abs(evt.delta.x), 0), + size: [raster.bounds.width + evt.delta.x, raster.bounds.height] + }); + main = raster.getSubRaster(mainRect); + + // NOTE: This rect is from the perspective of the Raster (not the overall view/canvas) + const subBounds = new Rectangle({ + point: new Point(0, 0), + size: [Math.abs(evt.delta.x), raster.bounds.height] + }); + sub = raster.getSubRaster(subBounds); + + main.translate(new Point(evt.delta.x, 0)); + sub.translate(new Point(main.bounds.width, 0)); + + ud.offset += evt.delta.x; + } else if (evt.delta.x > 0) { + // shifting to the right + + // NOTE: This rect is from the perspective of the Raster (not the overall view/canvas) + const mainRect = new Rectangle({ + point: new Point(0, 0), + size: [raster.bounds.width - evt.delta.x, raster.bounds.height] + }); + main = raster.getSubRaster(mainRect); + + // NOTE: This rect is from the perspective of the Raster (not the overall view/canvas) + const subBounds = new Rectangle({ + point: new Point(raster.bounds.width - evt.delta.x, 0), + size: [evt.delta.x, raster.bounds.height] + }); + sub = raster.getSubRaster(subBounds); + + main.translate(new Point(evt.delta.x, 0)); + sub.translate(new Point(main.bounds.width * -1, 0)); + + ud.offset += evt.delta.x; + } + // else - do nothing, no change in x value + + // Place both Rasters instance in a Group instance and rasterize the group to + // continuously have only a single image/raster + const g = new Group({ + insert: false, + children: [main, sub] + }); + const rast = (g.rasterize.bind(g, null, false))(); // defined this way because TypeScript is dumb + raster.replaceWith(rast); + ud.raster = rast; + + drawFn(); + + isUpdating = false; + + //adjust the annotations + for (const layer of ud.project.layers) { + if (layer.name != undefined + && layer.name == 'unroll-annotations') { + + for (const group of layer.children) { + + for (const point of group.children) { + point.position.x += evt.delta.x; + + if (point.position.x <= 15) { + point.position.x = (ud.raster.bounds.width + 15) - (15 - point.position.x); + } else if (point.position.x >= (ud.raster.bounds.width + 15)) { + point.position.x = point.position.x - ud.raster.bounds.width; + } + }//end of for points + }//end fo for group + }//end of if unroll layer + }//end of for layers + } + }); + } + + drawUnrollCanvas(ud: UnrollDisplay, colorChannel: number[]) { + ud.project = this.paperSvc.addCanvas(ud.canvas); + ud.project.activeLayer.addChild(ud.raster); + + UnrollCompareDialogComponent.tintRaster(ud.raster, colorChannel); + ud.raster.position = ud.project.view.center; + } + + probeInit(canvasRef) { + this.probe.canvas = canvasRef.nativeElement; + } + + candidateInit(canvasRef) { + this.candidate.canvas = canvasRef.nativeElement; + } + + drawOverlay() { + const overlayFactor = 2.0; + this.overlay.project.activeLayer.removeChildren(); + + const probe = this.probe.raster.clone({insert: false}); + probe.name = 'probe'; + probe.opacity = 1 - this.slider.value; + + const candidate = this.candidate.raster.clone({insert: false}); + candidate.name = 'candidate'; + candidate.opacity = this.slider.value; + + this.overlay.project.activeLayer.addChildren([candidate, probe]); + + probe.scale(overlayFactor); + candidate.scale(overlayFactor); + + if (this._showProbeAnnotations) { + for (const annotation of this.probePointAnnotations) { + const newPt = this._input.probe.toolManager.mainToUnroll(new Point(annotation.value.points[0].x, annotation.value.points[0].y)); + if (newPt.x == -1 || newPt.y == -1) { +continue; +} + + //adjust for the 15 px boundary on the unroll compare modal + newPt.x *= overlayFactor; + newPt.y *= overlayFactor; + + newPt.x += this.divAdjustX + (this.probe.offset * overlayFactor); + newPt.y += this.divAdjustY + this.probeHeightOffset; + + if (newPt.x <= this.divAdjustX) { + newPt.x = (probe.bounds.width + this.divAdjustX) - (this.divAdjustX - newPt.x); + } else if (newPt.x >= (probe.bounds.width + this.divAdjustX)) { + newPt.x = newPt.x - probe.bounds.width; + } + + const circle = new Path.Circle(newPt, annotation.value.data['radius']); + circle.fillColor = annotation.value.data['fillColor']; + circle.strokeWidth = annotation.value.data['strokeWidth']; + circle.strokeColor = annotation.value.data['strokeColor']; + this.overlay.project.activeLayer.addChild(circle); + } + } + + if (this._showCandidateAnnotations) { + for (const annotation of this.candidatePointAnnotations) { + const newPt = this._input.candidate.toolManager.mainToUnroll(new Point(annotation.value.points[0].x, annotation.value.points[0].y)); + + if (newPt.x == -1 || newPt.y == -1) { +continue; +} + + //adjust for the 15 px boundary on the unroll compare modal + newPt.x *= overlayFactor; + newPt.y *= overlayFactor; + + newPt.x += this.divAdjustX + (this.candidate.offset * overlayFactor); + newPt.y += this.divAdjustY + this.candidateHeightOffset; + + if (newPt.x <= this.divAdjustX) { + newPt.x = (candidate.bounds.width + this.divAdjustX) - (this.divAdjustX - newPt.x); + } else if (newPt.x >= (candidate.bounds.width + this.divAdjustX)) { + newPt.x = newPt.x - candidate.bounds.width; + } + + const circle = new Path.Circle(newPt, annotation.value.data['radius']); + circle.fillColor = annotation.value.data['fillColor']; + circle.strokeWidth = annotation.value.data['strokeWidth']; + circle.strokeColor = annotation.value.data['strokeColor']; + this.overlay.project.activeLayer.addChild(circle); + } + } + + if (this._showProbeAnnotations && this._showCandidateAnnotations && this._showClosePoints > 0) { + const annotationsUsed = []; + + for (const pAnn of this.probePointAnnotations) { + const currentPair = new ClosestPair(); + currentPair.distance = Number.MAX_SAFE_INTEGER; + + if (annotationsUsed.indexOf(pAnn) < 0) { + for (const cAnn of this.candidatePointAnnotations) { + if (annotationsUsed.indexOf(cAnn) < 0) { + const pPoint = this._input.probe.toolManager.mainToUnroll(new Point(pAnn.value.points[0].x, pAnn.value.points[0].y)); + const cPoint = this._input.candidate.toolManager.mainToUnroll(new Point(cAnn.value.points[0].x, cAnn.value.points[0].y)); + + if (pPoint.x == -1 || pPoint.y == -1) { +continue; +} + + //adjust for the 15 px boundary on the unroll compare modal + pPoint.x *= overlayFactor; + pPoint.y *= overlayFactor; + + pPoint.x += this.divAdjustX + (this.probe.offset * overlayFactor); + pPoint.y += this.divAdjustY + this.probeHeightOffset; + + if (pPoint.x <= this.divAdjustX) { + pPoint.x = (probe.bounds.width + this.divAdjustX) - (this.divAdjustX - pPoint.x); + } else if (pPoint.x >= (probe.bounds.width + this.divAdjustX)) { + pPoint.x = pPoint.x - probe.bounds.width; + } + + if (cPoint.x == -1 || cPoint.y == -1) { +continue; +} + + //adjust for the 15 px boundary on the unroll compare modal + cPoint.x *= overlayFactor; + cPoint.y *= overlayFactor; + + cPoint.x += this.divAdjustX + (this.candidate.offset * overlayFactor); + cPoint.y += this.divAdjustY + this.candidateHeightOffset; + + if (cPoint.x <= this.divAdjustX) { + cPoint.x = (candidate.bounds.width + this.divAdjustX) - (this.divAdjustX - cPoint.x); + } else if (cPoint.x >= (candidate.bounds.width + this.divAdjustX)) { + cPoint.x = cPoint.x - candidate.bounds.width; + } + + const tempDist = pPoint.getDistance(cPoint); + if (tempDist <= this._showClosePoints && tempDist < currentPair.distance) { + currentPair.point1 = pPoint; + currentPair.point2 = cPoint; + currentPair.anno1 = pAnn; + currentPair.anno2 = cAnn; + currentPair.distance = tempDist; + } + } + } + } + + if (currentPair.distance < this._showClosePoints) { + annotationsUsed.push(currentPair.anno1); + annotationsUsed.push(currentPair.anno2); + + const path = new Path(); + path.strokeColor = new Color('#FFFF00'); + path.strokeWidth = 2; + path.add(currentPair.point1); + path.add(currentPair.point2); + + this.overlay.project.activeLayer.addChild(path); + } + } + } + + const probePosition = new Point(this.overlay.project.view.center.x, + this.overlay.project.view.center.y + this.probeHeightOffset); + const candidatePosition = new Point(this.overlay.project.view.center.x, + this.overlay.project.view.center.y + this.candidateHeightOffset); + probe.position = probePosition; + candidate.position = candidatePosition; + } + + overlayInit(canvasRef) { + this.overlay.project = this.paperSvc.addCanvas(canvasRef.nativeElement); + + this.drawOverlay(); + } + + updateOverlay(overlayUpdate) { + if (this.previousOverlayValue != overlayUpdate) { + this.resetCanvas(); + + jQuery('#probeAnnotationsLabel').removeClass('btn-primary'); + jQuery('#probeAnnotationsLabel').removeClass('btn-primary'); + jQuery('#probeAnnotationsLabel').removeClass('btn-danger'); + + jQuery('#candidateAnnotationsLabel').removeClass('btn-success'); + jQuery('#candidateAnnotationsLabel').removeClass('btn-warning'); + jQuery('#candidateAnnotationsLabel').removeClass('btn-primary'); + + if (overlayUpdate == 1) { + this.drawCompareItems([1.25, 0.75, 0.75], [0.75, 1.25, 0.75]); + + jQuery('#probeAnnotationsLabel').addClass('btn-danger'); + jQuery('#candidateAnnotationsLabel').addClass('btn-success'); + + } else if (overlayUpdate == 2) { + this.drawCompareItems([.75, 0.75, 1.25], [1.25, 1.25, 0.75]); + + jQuery('#probeAnnotationsLabel').addClass('btn-primary'); + jQuery('#candidateAnnotationsLabel').addClass('btn-warning'); + } else { + this.drawCompareItems([1, 1, 1], [1, 1, 1]); + + jQuery('#probeAnnotationsLabel').addClass('btn-primary'); + jQuery('#candidateAnnotationsLabel').addClass('btn-primary'); + } + + this.drawOverlay(); + this.previousOverlayValue = overlayUpdate; + } + } + + drawCompareItems(probeColorChannel, candidateColorChannel) { + this.drawUnrollCanvas(this.probe, probeColorChannel); + this.drawUnrollCanvas(this.candidate, candidateColorChannel); + + this.probe.project.addLayer(this.probeLayer); + this.candidate.project.addLayer(this.candidateLayer); + + UnrollCompareDialogComponent.drawSliderControls(this.probe, this.drawOverlay.bind(this)); + UnrollCompareDialogComponent.drawSliderControls(this.candidate, this.drawOverlay.bind(this)); + + this.probe.loading = false; + this.candidate.loading = false; + } + + showProbeAnnotations() { + this._showProbeAnnotations = !this._showProbeAnnotations; + this.drawOverlay(); + } + + showClosePoints() { + this.drawOverlay(); + } + + showCandidateAnnotations() { + this._showCandidateAnnotations = !this._showCandidateAnnotations; + this.drawOverlay(); + } + + moveProbeUp() { + if (this.probeHeightOffset > -10) { + this.probeHeightOffset -= 1; + } + this.drawOverlay(); + } + + moveProbeDown() { + if (this.probeHeightOffset < 10) { + this.probeHeightOffset += 1; + } + this.drawOverlay(); + } + + moveCandidateUp() { + if (this.candidateHeightOffset > -20) { + this.candidateHeightOffset -= 1; + } + this.drawOverlay(); + } + + moveCandidateDown() { + if (this.candidateHeightOffset < 20) { + this.candidateHeightOffset += 1; + } + this.drawOverlay(); + } + + resetHeights() { + this.candidateHeightOffset = 0; + this.probeHeightOffset = 0; + this.drawOverlay(); + } + + closeDialog() { + this.dialogRef.close(); + } + +} diff --git a/web/client/src/app/save-dialog/save-dialog.component.css b/web/client/src/app/save-dialog/save-dialog.component.css new file mode 100644 index 0000000..0edc86e --- /dev/null +++ b/web/client/src/app/save-dialog/save-dialog.component.css @@ -0,0 +1,13 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +.warning-message { + color: #a94442; +} diff --git a/web/client/src/app/save-dialog/save-dialog.component.html b/web/client/src/app/save-dialog/save-dialog.component.html new file mode 100644 index 0000000..a250d17 --- /dev/null +++ b/web/client/src/app/save-dialog/save-dialog.component.html @@ -0,0 +1,34 @@ + + + + diff --git a/web/client/src/app/save-dialog/save-dialog.component.spec.ts b/web/client/src/app/save-dialog/save-dialog.component.spec.ts new file mode 100644 index 0000000..13d9c32 --- /dev/null +++ b/web/client/src/app/save-dialog/save-dialog.component.spec.ts @@ -0,0 +1,45 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { SaveDialogComponent } from './save-dialog.component'; +import { BsModalService } from 'ngx-bootstrap/modal'; + +describe('XXVI. The SaveDialogComponent class:', () => { + let component: SaveDialogComponent; + let fixture: ComponentFixture; + let modalService: jasmine.SpyObj; + + beforeEach(waitForAsync(() => { + modalService = jasmine.createSpyObj('BsModalService', ['show']); + modalService.show.and.returnValue({hide: jasmine.createSpy('hide')} as any); + + TestBed.configureTestingModule({ + declarations: [SaveDialogComponent], + providers: [ + {provide: BsModalService, useValue: modalService} + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SaveDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('a. show takes zero parameters and returns a void type, but prints the modalRef to console.', () => { + const res = component.show(); + expect(res).toBeUndefined(); + expect(modalService.show).toHaveBeenCalledWith(component.saveModal); + }); + +}); diff --git a/web/client/src/app/save-dialog/save-dialog.component.ts b/web/client/src/app/save-dialog/save-dialog.component.ts new file mode 100644 index 0000000..873bad2 --- /dev/null +++ b/web/client/src/app/save-dialog/save-dialog.component.ts @@ -0,0 +1,56 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component, Injectable, TemplateRef, ViewChild } from '@angular/core'; +import { Subject } from 'rxjs'; +import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; + +@Component({ + selector: 'app-save-dialog', + templateUrl: './save-dialog.component.html', + styleUrls: ['./save-dialog.component.css'] +}) +@Injectable({ + providedIn: 'root' +}) +export class SaveDialogComponent { + closed$: Subject = new Subject(); + save = false; + continue = false; + modalRef: BsModalRef; + @ViewChild('appSaveModal') saveModal: TemplateRef; + + constructor(private modalService: BsModalService) { + } + + show() { + this.modalRef = this.modalService.show(this.saveModal); + } + + hide() { + this.save = false; + this.continue = false; + this.closed$.next(true); + this.modalRef.hide(); + } + + saveClicked() { + this.save = true; + this.continue = true; + this.modalRef.hide(); + this.closed$.next(true); + } + + continueClicked() { + this.save = false; + this.continue = true; + this.closed$.next(true); + } +} diff --git a/web/client/src/app/service-status/service-status.component.css b/web/client/src/app/service-status/service-status.component.css new file mode 100644 index 0000000..9310f10 --- /dev/null +++ b/web/client/src/app/service-status/service-status.component.css @@ -0,0 +1,53 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +ul.file-list-group { + border-radius: .25rem; + border-top: 1px solid rgba(0, 0, 0, .125); + border-bottom: 1px solid rgba(0, 0, 0, .125); +} + +.file-list-group li:first-child { + border-top: none; + +} + +.file-list-group li:last-child { + border-bottom: none; +} + +.mat-column-service { + text-align: left; + padding-right: 25px; +} + +.mat-column-status { + text-align: center; +} + +.status-Error { + color: red; +} + +.status-Loading { + color: transparent; +} + +.status-Online { + color: green; +} + +.status-Disabled { + color: lightgray; +} + +.version-info { + font-size: 10px; +} diff --git a/web/client/src/app/service-status/service-status.component.html b/web/client/src/app/service-status/service-status.component.html new file mode 100644 index 0000000..debe4a1 --- /dev/null +++ b/web/client/src/app/service-status/service-status.component.html @@ -0,0 +1,42 @@ + + + + +
+ + + + + + + + + + + +
Service + {{service.displayName | titlecase}} +
v{{service.version}}
+
Status + +
+ +
+ {{serviceStatusService.systemDownMessage}} +
+
+
+
diff --git a/web/client/src/app/service-status/service-status.component.spec.ts b/web/client/src/app/service-status/service-status.component.spec.ts new file mode 100644 index 0000000..e8c2389 --- /dev/null +++ b/web/client/src/app/service-status/service-status.component.spec.ts @@ -0,0 +1,43 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatButtonModule } from '@angular/material/button'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTableModule } from '@angular/material/table'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ServiceStatusComponent } from './service-status.component'; + +describe('XXVII. The ServiceStatusComponent class:', () => { + let component: ServiceStatusComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, MatButtonModule, MatMenuModule, MatSnackBarModule, MatTableModule, MatTooltipModule], + declarations: [ServiceStatusComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ServiceStatusComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('a. setInterval uses a callback function to check the service status of the component. Otherwise it times out.', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/web/client/src/app/service-status/service-status.component.ts b/web/client/src/app/service-status/service-status.component.ts new file mode 100644 index 0000000..b888528 --- /dev/null +++ b/web/client/src/app/service-status/service-status.component.ts @@ -0,0 +1,37 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core'; +import { MatTableDataSource } from '@angular/material/table'; +import { ServiceStatusService } from './service-status.service'; + +@Component({ + selector: 'app-service-status', + templateUrl: './service-status.component.html', + styleUrls: ['./service-status.component.css'] +}) + +export class ServiceStatusComponent implements OnInit { + currentUserId = 0; + serviceDataSource: MatTableDataSource; + + private servicePollingInterval = 10000; // (5000 = 5s) + @Input() visibleColumns: string[]; + + @Output() finishedLoading: EventEmitter = new EventEmitter(); + + constructor(public serviceStatusService: ServiceStatusService) { + } + + ngOnInit() { + this.serviceStatusService.checkServicesStatus(); // Initial run that kicks off right away without waiting 10 seconds + setInterval(() => this.serviceStatusService.checkServicesStatus(), this.servicePollingInterval); + } +} diff --git a/web/client/src/app/service-status/service-status.service.ts b/web/client/src/app/service-status/service-status.service.ts new file mode 100644 index 0000000..4ef8ba9 --- /dev/null +++ b/web/client/src/app/service-status/service-status.service.ts @@ -0,0 +1,330 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { MatTableDataSource } from '@angular/material/table'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { + IwpServiceProperties, + IwpServiceStatus, + ServiceStatusNameType, + ServiceStatusStatusType +} from '../types/service-status'; +import { VersionInfo } from '../types/versionInfo'; + +@Injectable( + {providedIn: 'root'} +) +export class ServiceStatusService { + + servicesServiceUrl = '/api/activeMQ/services/status'; + servicesPropertiesUrl = '/api/service/status'; + serviceVersionUrl = '/api/service/versions'; + + // Property for flagging when even the URL that checks check services are available is down + serviceStatusDown = false; + systemDownMessage: string; + + serviceDataSource: MatTableDataSource; + versionInfo: VersionInfo; + _iwpServices: IwpServiceStatus[] = []; + iwpFinalServices: IwpServiceStatus[] = []; + previousServiceStatus: IwpServiceStatus[] = []; + overallServiceStatus: ServiceStatusStatusType = ServiceStatusStatusType.Loading; + + constructor(private http: HttpClient, + private matSnackBar: MatSnackBar) { + } + + updateServiceVersions(): void { + this.http.get(this.serviceVersionUrl).subscribe(vi => { + this.versionInfo = vi; + }); + } + + getBackendServices(): Observable { + return this.http.get(this.servicesServiceUrl); + } + + getServicesProperties(): Observable { + return this.http.get(this.servicesPropertiesUrl); + } + + public isServiceDisabled(serviceName: string): boolean { + const newStatus = this.iwpFinalServices.find(fStatus => fStatus.name === serviceName); + if (!!newStatus && newStatus.status == ServiceStatusStatusType.Disabled) { + return true; + } + return false; + } + + checkServicesStatus(): void { + this._iwpServices = []; + this.iwpFinalServices = []; + + this.getBackendServices().subscribe(data => { + if (data === null) { + this.markAllServicesDown('ActiveMQ is down, contact your system administrator.'); + } else { + for (const serviceData of data) { + this._setIwpServicesArray(serviceData); + } + this.getServicesProperties().subscribe(propertyData => { + this._iwpServices.forEach(iwpItem => { + const matchedItem = propertyData.find(propData => propData.serviceName === iwpItem.name); + if (!!matchedItem && matchedItem.serviceExcluded == false) { + this.setIwpFinalServicesStatus(iwpItem, matchedItem); + } + }); + // Sort the services into alphabetical order + this.iwpFinalServices.sort((a, b) => a.name.localeCompare(b.name)); + + this.comparePreviousStatus(); + this.setOverallServiceStatus(); + + // Push in a service definition for the front and backend manually so we can report out their version numbers + this.iwpFinalServices.push({ + name: ServiceStatusNameType.webbackend, + displayName: 'Web Backend', + statusHint: this.getMessageForStatus(ServiceStatusStatusType.Online), + status: ServiceStatusStatusType.Online, + version: this.versionInfo.iwpVersion, + consumerCount: null, + enqueueCount: null, + dequeueCount: null, + listenerType: null, + serviceDefinition: 'Serves the backend API to the IRIS web application.' + }); + this.iwpFinalServices.push({ + name: ServiceStatusNameType.webfrontend, + displayName: 'Web Frontend', + statusHint: this.getMessageForStatus(ServiceStatusStatusType.Online), + status: ServiceStatusStatusType.Online, + version: this.versionInfo.uiVersion, + consumerCount: null, + enqueueCount: null, + dequeueCount: null, + listenerType: null, + serviceDefinition: 'Serves the IRIS web application user interface.' + }); + + this.serviceDataSource = new MatTableDataSource(this.iwpFinalServices); + }); + } + }, error => { + this.markAllServicesDown('All services are down; contact your system administrator.'); + }); + } + + private markAllServicesDown(message: string) { + this.serviceStatusDown = true; + this.overallServiceStatus = ServiceStatusStatusType.Error; + this.systemDownMessage = message; + this.matSnackBar.open(message, 'Reload Page', {duration: 0}).onAction().subscribe(() => window.location.reload()); + } + + _setIwpServicesArray(service: IwpServiceStatus) { + const fullName = service.name.split('.'); + let listenerName = fullName[1]; + let listenerType = fullName[2]; + let serviceDef = null; + const displayName = listenerName; + + if (listenerName == ServiceStatusNameType.tshepii) { + serviceDef = 'Detects crypts and unrolls the location for the iris'; + } else if (listenerName == ServiceStatusNameType.acii) { + serviceDef = 'Suggests the orientation of an image: Left or Right'; + } else if (listenerName == ServiceStatusNameType.annotation) { + serviceDef = 'Provides automated labeling for image segments'; + } else if (listenerName == ServiceStatusNameType.biqt) { + serviceDef = 'Provides the image with a quality score and suggests the presence of contacts: Clear or Cosmetic'; + } else if (listenerName == ServiceStatusNameType.pdm) { + serviceDef = 'Changes the ratio of the pupil to iris by dilating pupil.'; + } + + const serviceStatus: IwpServiceStatus = { + name: listenerName, + displayName, + status: null, + consumerCount: service.consumerCount, + enqueueCount: service.enqueueCount, + dequeueCount: service.dequeueCount, + listenerType, + statusHint: null, + serviceDefinition: serviceDef, + version: this.versionInfo.serviceVersions[listenerName] + }; + this._iwpServices.push(serviceStatus); + } + + setIwpFinalServicesStatus(iwpItem: IwpServiceStatus, propertyItem: IwpServiceProperties): void { + let status = null; + let errHint = null; + let responseQueue = null; + let requestQueue = null; + + if (propertyItem.serviceEnabled == false) { + status = ServiceStatusStatusType.Disabled; + errHint = this.getMessageForStatus(ServiceStatusStatusType.Disabled); + } else { + // Checking for listeners of each service + this._iwpServices.forEach(item => { + if (item.name == iwpItem.name && item.listenerType == 'response') { + responseQueue = item; + } + if (item.name == iwpItem.name && item.listenerType == 'request') { + requestQueue = item; + } + }); + + // If request and response queues are present AND both have listeners - Not a problem + // Service Property is true, and service is running + if (!!requestQueue && !!responseQueue) { + if (requestQueue.consumerCount != 0 && responseQueue.consumerCount != 0) { + status = ServiceStatusStatusType.Online; + errHint = this.getMessageForStatus(ServiceStatusStatusType.Online); + } else if ((requestQueue.consumerCount != 0 && responseQueue.consumerCount == 0) || + (requestQueue.consumerCount == 0 && responseQueue.consumerCount == 0)) { + // If request and response queues are present AND request listener = 1 AND response listener = 0 - Not a problem + // Service Property is false, service is running + // If both queues are present and both listeners = 0 Not a problem + status = ServiceStatusStatusType.Disabled; + errHint = this.getMessageForStatus(ServiceStatusStatusType.Disabled); + } else if (requestQueue.consumerCount == 0 && responseQueue.consumerCount != 0) { + // If request and response queues are present AND request listener = 0 AND response listener = 1 - Problem + // Service Property is true, service is NOT running + status = ServiceStatusStatusType.Error; + errHint = this.getMessageForStatus(ServiceStatusStatusType.Error); + } + } else if (requestQueue == null && !!responseQueue && responseQueue.consumerCount != 0) { + // If response queues is present AND response listener = 1 AND request queue is missing - Problem + // Service Property is true, service is NOT running + status = ServiceStatusStatusType.Error; + errHint = this.getMessageForStatus(ServiceStatusStatusType.Error); + } else if (requestQueue == null && responseQueue == null) { + status = ServiceStatusStatusType.Disabled; + errHint = this.getMessageForStatus(ServiceStatusStatusType.Disabled); + } + } + + const updatedService = { + name: iwpItem.name, + displayName: iwpItem.displayName, + status, + consumerCount: iwpItem.consumerCount, + enqueueCount: iwpItem.enqueueCount, + dequeueCount: iwpItem.dequeueCount, + listenerType: null, + statusHint: errHint, + serviceDefinition: iwpItem.serviceDefinition, + version: this.versionInfo.serviceVersions[iwpItem.name] + }; + + const serviceIndex = this.iwpFinalServices.findIndex(service => service.name == updatedService.name); + if (serviceIndex === -1) { + this.iwpFinalServices.push(updatedService); + } + } + + private getMessageForStatus(statusType: ServiceStatusStatusType): string { + switch (statusType) { + case ServiceStatusStatusType.Online: { + return 'Online: Service is available'; + } + case (ServiceStatusStatusType.Disabled): { + return 'Disabled: Service is disabled in IWP Properties file'; + } + case (ServiceStatusStatusType.Loading): { + return 'Loading: Service is initializing'; + } + case (ServiceStatusStatusType.Error): { + return 'Error: Service has encountered an issue'; + } + default: { + return 'Unknown Status'; + } + + } + } + + comparePreviousStatus(): void { + const statusChanges: IwpServiceStatus[] = []; + + if (this.previousServiceStatus.length > 0) { + this.previousServiceStatus.forEach(prevStatus => { + const newStatus = this.iwpFinalServices.find(fStatus => fStatus.name === prevStatus.name); + if (!!newStatus && newStatus.status != prevStatus.status) { + statusChanges.push(newStatus); + } + }); + + if (statusChanges.length > 0) { + this.showSnackBarNotification(statusChanges); + } + } + + this.previousServiceStatus = JSON.parse(JSON.stringify(this.iwpFinalServices)); + + } + + showSnackBarNotification(newStatuses: IwpServiceStatus[]): void { + const messages: string[] = []; + this.overallServiceStatus = ServiceStatusStatusType.Loading; + for (const status of newStatuses) { + const messageToAdd = status.name + '\'s status has changed to ' + status.status + '.\n\n'; + messages.push(messageToAdd); + } + const messageToPost = messages.join(''); + + this.matSnackBar.open(messageToPost, 'Dismiss'); + } + + setOverallServiceStatus() { + let isError = 0; + let isDisabled = 0; + let isOnline = 0; + for (const service of this.iwpFinalServices) { + if (service.status == ServiceStatusStatusType.Error) { + isError++; + } else if (service.status == ServiceStatusStatusType.Disabled) { + isDisabled++; + } else if (service.status == ServiceStatusStatusType.Online) { + isOnline++; + } + } + + if (isError >= 1) { + this.overallServiceStatus = ServiceStatusStatusType.Error; + } else if (isDisabled == 5) { + this.overallServiceStatus = ServiceStatusStatusType.Disabled; + } else if (isError == 0 && isDisabled < 5 && isOnline >= 1) { + this.overallServiceStatus = ServiceStatusStatusType.Online; + } else { + this.overallServiceStatus = ServiceStatusStatusType.Loading; + } + } + + getServices(): IwpServiceStatus[] { + return this.iwpFinalServices; + } + + getNonWebServices(): IwpServiceStatus[] { + let nonwebServices: IwpServiceStatus[] = this.iwpFinalServices.slice(); + + //take out the web services from display + nonwebServices = nonwebServices.filter((obj) => (obj.name !== ServiceStatusNameType.webbackend)); + + return nonwebServices.filter((obj) => (obj.name != ServiceStatusNameType.webfrontend)); + + } + +} diff --git a/web/client/src/app/tshepii/closest-pair.ts b/web/client/src/app/tshepii/closest-pair.ts new file mode 100644 index 0000000..1ab1ae2 --- /dev/null +++ b/web/client/src/app/tshepii/closest-pair.ts @@ -0,0 +1,20 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import Point = paper.Point; +import { Annotation } from '../types/annotation'; + +export class ClosestPair { + distance: number; + point1: Point; + point2: Point; + anno1: Annotation; + anno2: Annotation; +} diff --git a/web/client/src/app/tshepii/iwp-point.ts b/web/client/src/app/tshepii/iwp-point.ts new file mode 100644 index 0000000..6135bfe --- /dev/null +++ b/web/client/src/app/tshepii/iwp-point.ts @@ -0,0 +1,19 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +export class IwpPoint { + x: number; + y: number; + + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } +} diff --git a/web/client/src/app/tshepii/tshepii-compare-response.ts b/web/client/src/app/tshepii/tshepii-compare-response.ts new file mode 100644 index 0000000..8445115 --- /dev/null +++ b/web/client/src/app/tshepii/tshepii-compare-response.ts @@ -0,0 +1,18 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { ServiceResponse } from '../types/service-response'; +import { TshepiiResponse } from './tshepii-response'; + +export class TshepiiCompareResponse extends ServiceResponse { + candidateImageId: number; + probeResponse: TshepiiResponse; + candidateResponse: TshepiiResponse; +} diff --git a/web/client/src/app/tshepii/tshepii-response.ts b/web/client/src/app/tshepii/tshepii-response.ts new file mode 100644 index 0000000..1077320 --- /dev/null +++ b/web/client/src/app/tshepii/tshepii-response.ts @@ -0,0 +1,51 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { IwpPoint } from './iwp-point'; +import { ServiceResponse, ServiceStatus } from '../types/service-response'; + + +export class TshepiiResponse extends ServiceResponse { + cropCenter: IwpPoint; + originalIrisCenter: IwpPoint; + originalPupilCenter: IwpPoint; + cropRadius: number; + originalIrisRadius: number; + originalPupilRadius: number; + keyPoints: Array; + matchIndex: Array; + cryptList: Array>; + + constructor(cropRadius: number, + cropCenter: IwpPoint, + originalIrisRadius: number, + originalIrisCenter: IwpPoint, + originalPupilRadius: number, + originalPupilCenter: IwpPoint, + imageId: string, + error: string, + status: ServiceStatus, + ebtsImageId: string, + isResubmit: boolean + ) { + super(imageId, error, status, ebtsImageId, isResubmit); + this.cropCenter = cropCenter; + this.originalIrisCenter = originalIrisCenter; + this.originalPupilCenter = originalPupilCenter; + + this.cropRadius = cropRadius; + this.originalIrisRadius = originalIrisRadius; + this.originalPupilRadius = originalPupilRadius; + + this.keyPoints = new Array(); + this.cryptList = new Array>(); + this.matchIndex = new Array(); + } +} diff --git a/web/client/src/app/tshepii/tshepii.service.ts b/web/client/src/app/tshepii/tshepii.service.ts new file mode 100644 index 0000000..1ab5904 --- /dev/null +++ b/web/client/src/app/tshepii/tshepii.service.ts @@ -0,0 +1,101 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable, OnDestroy } from '@angular/core'; +import { Observable, timer, Subject, Subscription } from 'rxjs'; +import { switchMap, share, retry, takeUntil } from 'rxjs/operators'; +import { HttpClient } from '@angular/common/http'; +import { ServiceStatus } from '../types/service-response'; +import { TshepiiCompareResponse } from './tshepii-compare-response'; + +// Tshepii service is different than the other services. To get the Tshepii Comparison results, you need 2 images, and each image needs to have a Tshepii Response before it can get the Tshepii Comparison Response. +// When you upload an image into the probe/candidate canvas, it automatically gets sent to get the individual Tshepii Response. +@Injectable() +export class TshepiiService implements OnDestroy { + private tshepiiSnapshotUrl = '/api/service/tshepii/snapshot/'; + private tshepiiServiceStatusUrl = '/api/service/tshepii/status/'; + private tshepiiCompareServiceUrl = '/api/service/tshepii/compare/'; + public tshepiiResponseStatus$: Observable; + public tshepiiCompareStatus$: Observable; + private stopResponsePolling$ = new Subject(); + private stopComparePolling$ = new Subject(); + _tshepiiCompareSubscription: Subscription[] = []; + + constructor( + private http: HttpClient + ) { + } + + getTshepiiSnapshot(probeData: string, candidateData: string, probeEbtsImageId: string, candidateEbtsImageId: string): Observable { + let data = [probeData, candidateData] + return this.http.post(this.tshepiiSnapshotUrl + probeEbtsImageId + "/" + candidateEbtsImageId, data); + } + + getTshepiiResponseStatus(probeEbtsImageId: string, candidateEbtsImageId: string, probeModifiedImageId: string, candidateModifiedImageId: string): Observable { + return this.http.get(this.tshepiiServiceStatusUrl + probeEbtsImageId + '/' + candidateEbtsImageId + '/' + probeModifiedImageId + '/' + candidateModifiedImageId); + } + + // Usage of this function should poll this endpoint until it returns 'error', 'not sent', or 'Received' + // When we use this on the review page we should keep the tshepii button disabled until we've gotten a response of 'Received' + // From the review component start calling this function after the user has uploaded their report (or at least two images?) + startTshepiiResponseStatusPolling(probeEbtsImageId: string, candidateEbtsImageId: string, probeModifiedImageId: string, candidateModifiedImageId: string): void { + this.tshepiiResponseStatus$ = timer(1, 5000).pipe( //5000 = 5 second polling interval + switchMap(() => + this.getTshepiiResponseStatus(probeEbtsImageId, candidateEbtsImageId, probeModifiedImageId, candidateModifiedImageId) + ), + retry(4), + share(), + takeUntil(this.stopResponsePolling$) + ); + } + + stopTshepiiResponseStatusPolling(): void { + this.stopResponsePolling$.next(true); + this.stopResponsePolling$.complete(); + } + + getTshepiiComparison(probeImageId: string, candidateImageId: string, isResubmit: boolean): Observable { + return this.http.get(this.tshepiiCompareServiceUrl + probeImageId + '/' + candidateImageId + '/' + isResubmit + ); + } + + getTshepiiCompareStatus(probeEbtsImageId: string, candidateEbtsImageId: string, probeModifiedImageId: string, candidateModifiedImageId: string): Observable { + return this.http.get(this.tshepiiCompareServiceUrl + 'status/' + probeEbtsImageId + '/' + candidateEbtsImageId + '/' + probeModifiedImageId + '/' + candidateModifiedImageId); + } + + startTshepiiCompareStatusPolling(probeEbtsImageId: string, candidateEbtsImageId: string, probeModifiedImageId: string, candidateModifiedImageId: string): void { + this.tshepiiCompareStatus$ = timer(1, 5000).pipe( //5000 = 5 second polling interval + switchMap(() => + this.getTshepiiCompareStatus(probeEbtsImageId, candidateEbtsImageId, probeModifiedImageId, candidateModifiedImageId) + ), + retry(4), + share(), + takeUntil(this.stopComparePolling$) + ); + } + + stopTshepiiCompareStatusPolling(imageId?: string): void { + if (imageId) { + this._tshepiiCompareSubscription[imageId].unsubscribe(); + } else { + this.stopComparePolling$.next(true); + this.stopComparePolling$.complete(); + this._tshepiiCompareSubscription.forEach(subscription => { + subscription.unsubscribe(); + }); + } + } + + ngOnDestroy() { + this.stopTshepiiResponseStatusPolling(); + this.stopTshepiiCompareStatusPolling(); + } + +} diff --git a/web/client/src/app/types/annotation-value.ts b/web/client/src/app/types/annotation-value.ts new file mode 100644 index 0000000..edde8b4 --- /dev/null +++ b/web/client/src/app/types/annotation-value.ts @@ -0,0 +1,17 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Point } from 'paper'; + + +export class AnnotationValue { + public points: InstanceType[]; + public data: object; +} diff --git a/web/client/src/app/types/annotation.ts b/web/client/src/app/types/annotation.ts new file mode 100644 index 0000000..508afb7 --- /dev/null +++ b/web/client/src/app/types/annotation.ts @@ -0,0 +1,228 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import * as short from 'short-uuid'; + +import { LinkedMap } from './linked-map'; +import { JsonProperty } from '../utils/decorators/json-property'; +import { MapUtils } from '../utils/map-utils'; +import { parse, stringify } from 'flatted/esm'; +import { AnnotationValue } from './annotation-value'; + +export enum AnnotationType { + POINT = 0, + CIRCLE = 1, + POLYGON = 2, + PENCIL = 3, + UNKNOWN = 4, + CLAHE = 5, + BRIGHTNESS = 6, + CONTRAST = 7, + CHANNEL = 8, + COLOR_LEVEL = 9, + CROP = 10, + ROTATE = 11, + SHARPEN = 12, + RESIZE = 13, + MIRROR = 14, + GROUP = 15, + EYELID = 16, + PDM = 17, + DUAL_PDM = 18, + // For grouping +} + +export const ANNO_ICON_MAPPING = { + [AnnotationType.POINT]: 'fa-map-pin', + [AnnotationType.CIRCLE]: 'fa-circle', + [AnnotationType.POLYGON]: 'fa-bookmark', + [AnnotationType.PENCIL]: 'fa-pencil-alt', + [AnnotationType.UNKNOWN]: 'fa-question-circle', + [AnnotationType.CLAHE]: 'fas fa-th', + [AnnotationType.BRIGHTNESS]: 'fa-sun-o', + [AnnotationType.CONTRAST]: 'fa-adjust', + [AnnotationType.CHANNEL]: 'fa-bar-chart', + [AnnotationType.COLOR_LEVEL]: 'fa-pie-chart', + [AnnotationType.CROP]: 'fa-crop', + [AnnotationType.ROTATE]: 'fa-redo', + [AnnotationType.SHARPEN]: 'fa-sliders', + [AnnotationType.RESIZE]: 'fa-object-ungroup', + [AnnotationType.MIRROR]: 'fa-exchange', + [AnnotationType.GROUP]: 'fa-folder-o', + [AnnotationType.EYELID]: 'fa-smile-beam', + [AnnotationType.PDM]: 'all_out' +}; + +export class Annotation { + id: string; + text: string; + type: AnnotationType; + creationDateEpoch: number; + drawColor: string; + fillColor: string; + annoIcon: string; + iconType: string = "fa"; + isSystemGenerated = false; + + public value: AnnotationValue; + + parentId = ''; + + // This is a workaround for saving a List instead of LinkedMap + // on the server-side. The problem is, since LinkedMap is a doubly + // linked list, it causes a circular reference that needs a custom + // deseraializer on the backend. To avoid that, we have a dummy + // List to pass onto the server when saving the Annotations. + @JsonProperty({clazz: Annotation}) + tempChildren: Annotation[]; + + children: LinkedMap; + + constructor(text?: string, type?: AnnotationType, annoIcon?: string, iconType?: string) { + this.id = short.generate(); + this.text = text ?? null; + this.type = type ?? null; + this.creationDateEpoch = Date.now(); + this.annoIcon = annoIcon ?? null; + this.iconType = iconType ?? "fa"; + this.drawColor = ''; + this.fillColor = ''; + this.value = new AnnotationValue(); + this.tempChildren = []; + this.children = new LinkedMap(); + } + + reassignId(): string { + const strId = short.generate(); + this.id = strId; + return this.id; + } + + clearChildren() { + return this.children.clearAll(); + } + + appendChildren(children: Array) { + for (const child of children) { + this.children.insert(child.id, child); + } + } + + addChild(child: Annotation, index?: number) { + this.children.insert(child.id, child, index); + return child.parentId = this.id; + } + + addChildBefore(beforeId: string, child: Annotation) { + this.children.insertBefore(beforeId, child.id, child); + return child.parentId = this.id; + } + + removeChild(thing: string | number) { + let removed; + + // Due to strict typing of TypeScript + if (typeof thing === 'string') { + removed = this.children.remove(thing); + } else if (typeof thing === 'number') { + removed = this.children.remove(thing); + } + + if (removed) { + removed.parentId = null; + } + return removed; + } + + getChildIndex(id: string): number { + return this.children.getIndex(id); + } + + getChildAfter(id: string) { + return this.children.getNext(id); + } + + getChildBefore(id: string) { + return this.children.getPrev(id); + } + + getFirstChild(): Annotation | null { + return this.children.getFirst(); + } + + hasChildren() { + return !this.children.isEmpty(); + } + + compareChildrenIndices(id1: string, id2: string) { + return this.children.compareIndices(id1, id2); + } + + getValueString(): string { + switch (+this.type) { + case AnnotationType.POINT: + case AnnotationType.CIRCLE: + return 'Center: (' + Math.floor(this.value.points[0].x) + ', ' + Math.floor(this.value.points[0].y) + ')'; + case AnnotationType.POLYGON: + break; + case AnnotationType.EYELID: + return 'Eyelid Location: ' + this.value.data['eyelidType']; + case AnnotationType.PENCIL: + return 'Color: ' + this.value.data['strokeColor']; + case AnnotationType.CLAHE: + return 'Clip Factor: ' + this.value.data['clipFactor']; + case AnnotationType.BRIGHTNESS: + return 'Value: ' + Math.round((+this.value.data['adjustment'] + 100) / 2.0); + case AnnotationType.CONTRAST: + return 'Value: ' + Math.round((+this.value.data['adjustment'] + 100) / 2.0); + case AnnotationType.CHANNEL: + return 'Channel: ' + this.value.data['channel']; + case AnnotationType.COLOR_LEVEL: + return 'Change RGB(' + this.value.data['red'] + ', ' + this.value.data['green'] + ', ' + this.value.data['blue'] + ')'; + case AnnotationType.CROP: + return 'Square( Center: (' + Math.floor(this.value.data['fromPointX']) + ', ' + + Math.floor(this.value.data['fromPointY']) + ') Width: ' + + Math.floor(this.value.data['width']) + ' Height: ' + + Math.floor(this.value.data['height']) + ')'; + case AnnotationType.ROTATE: + return 'Rotation: ' + (-1 * this.value.data['adjustment']); + case AnnotationType.SHARPEN: + return 'Value: ' + this.value.data['adjustment']; + case AnnotationType.RESIZE: + return 'Width: ' + Math.floor(this.value.data['width']) + ' Height: ' + + Math.floor(this.value.data['height']); + case AnnotationType.MIRROR: + return 'Orientation: ' + this.value.data['adjustment']; + case AnnotationType.PDM: + return 'Alpha Score: ' + this.value.data['alphaScore']; + default: + return ''; + } + return ''; + } + + static clone(anno: Annotation) { + const json = parse(stringify(anno)); + const cloned = MapUtils.deserialize(Annotation, json); + + const arr: any[] = []; + if (anno.hasChildren()) { + for (const child of anno.children!) { + + arr.push({ + key: child!.id, + value: this.clone(child!) + }); + } + } + cloned!.children = new LinkedMap(arr); + return cloned; + } +} diff --git a/web/client/src/app/types/candidate.ts b/web/client/src/app/types/candidate.ts new file mode 100644 index 0000000..ee7efad --- /dev/null +++ b/web/client/src/app/types/candidate.ts @@ -0,0 +1,35 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { EbtsImageData } from './image-data'; +import { JsonProperty } from '../utils/decorators/json-property'; + + +export class Candidate { + id: string; + score: number; + subjectIdentifier: string; + adjudicationResults: string; + currentImageIndex = -1; + + @JsonProperty({clazz: EbtsImageData}) + imageList: EbtsImageData[]; + + note: string; + + constructor() { + this.id = undefined; + this.score = 0; + this.subjectIdentifier = undefined; + this.adjudicationResults = undefined; + this.imageList = []; + this.note = undefined; + } +} diff --git a/web/client/src/app/types/case-information.ts b/web/client/src/app/types/case-information.ts new file mode 100644 index 0000000..5967a9a --- /dev/null +++ b/web/client/src/app/types/case-information.ts @@ -0,0 +1,31 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Person } from './person'; +import { JsonProperty } from '../utils/decorators/json-property'; + +export class CaseInformation { + @JsonProperty({clazz: Person}) + conductedBy: Person; + + @JsonProperty({clazz: Person}) + requestedBy: Person; + + comparisonDate: number; + receivedDate: number; + + + constructor() { + this.conductedBy = undefined; + this.requestedBy = undefined; + this.comparisonDate = new Date().getMilliseconds(); + this.receivedDate = new Date().getMilliseconds(); + } +} diff --git a/web/client/src/app/types/contact-type.ts b/web/client/src/app/types/contact-type.ts new file mode 100644 index 0000000..7e76db3 --- /dev/null +++ b/web/client/src/app/types/contact-type.ts @@ -0,0 +1,16 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +export enum ContactType { + None = 0, + Cosmetic = 1, + Clear = 2, + "" = 3, +} diff --git a/web/client/src/app/types/create-iris-search-request.ts b/web/client/src/app/types/create-iris-search-request.ts new file mode 100644 index 0000000..f0f83bf --- /dev/null +++ b/web/client/src/app/types/create-iris-search-request.ts @@ -0,0 +1,29 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { EbtsMessageRequest } from './ebts-message-request'; + +export class CreateIrisSearchRequest { + id: string; + message: EbtsMessageRequest; + userId: string; + creationDateEpoch: number; + isFinished: boolean; + filePath: string; + + constructor() { + this.id = ''; + this.message = new EbtsMessageRequest(); + this.userId = ''; + this.filePath = ''; + this.creationDateEpoch = 0; + this.isFinished = false; + } +} diff --git a/web/client/src/app/types/ebts-message-request.ts b/web/client/src/app/types/ebts-message-request.ts new file mode 100644 index 0000000..6746384 --- /dev/null +++ b/web/client/src/app/types/ebts-message-request.ts @@ -0,0 +1,53 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { EbtsImageData } from './image-data'; + + +export class EbtsMessageRequest { + id: string; + cinPrefix: string; // cin_pre mandatory 24 character + cinIdentifier: string; // cin_id mandatory 24 character + numberOfCandidates: number; // ncr default: 20 min: 2 max: 50 + caseExtension: number; // cix 2 to 4 byte mandatory + dateOfSubmission: string; // dat mandatory YYYYMMDD + destinationAgencyIdentifier: string; // dai mandatory + originatingAgencyIdentifier: string; // ori mandatory + transactionControlNumber: string; // tcn mandatory + attentionIndicator: string; // elr mandatory + sourceAgency: string; // src mandatory + irisCaptureDate: string; // icd mandatory YYYYMMDD + rotationOfEye: number; // rae optional + rotationUncertainty: number; // rau optional -- mandatory if rae is present + invalidFields: any; + errorMessages: string[]; + + imageData: EbtsImageData; + + constructor() { + this.id = ''; + this.cinPrefix = ''; + this.cinIdentifier = ''; + this.numberOfCandidates = 0; + this.caseExtension = 0; + this.dateOfSubmission = ''; + this.destinationAgencyIdentifier = ''; + this.originatingAgencyIdentifier = ''; + this.transactionControlNumber = ''; + this.attentionIndicator = ''; + this.sourceAgency = ''; + this.irisCaptureDate = ''; + this.rotationOfEye = 0; + this.rotationUncertainty = 0; + this.invalidFields = ''; + this.imageData = new EbtsImageData(); + this.errorMessages = Array(); + } +} diff --git a/web/client/src/app/types/eye-label.ts b/web/client/src/app/types/eye-label.ts new file mode 100644 index 0000000..e7ff21f --- /dev/null +++ b/web/client/src/app/types/eye-label.ts @@ -0,0 +1,16 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +export enum EyeLabel { + Undefined = 0, + Right = 1, + Left = 2, + "" = 3 +} diff --git a/web/client/src/app/types/image-data.ts b/web/client/src/app/types/image-data.ts new file mode 100644 index 0000000..2067def --- /dev/null +++ b/web/client/src/app/types/image-data.ts @@ -0,0 +1,62 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { EyeLabel } from './eye-label'; +import { Annotation } from './annotation'; +import { State } from './state'; +import { JsonProperty } from '../utils/decorators/json-property'; +import { ContactType } from './contact-type'; + +export class EbtsImageData { + id: string; + ebtsImageId: string; + unrollImageId: string; + imageData: string; + unrollImageData: string; + eyeLabel: EyeLabel; + detectedContact: boolean; + contactType: ContactType; + dme: string; + qualityScore = -1; + recordIndex = -1; + recordType = -1; + + @JsonProperty({clazz: Annotation}) + annotations: Annotation[]; + + @JsonProperty({clazz: State}) + pastStates: State[]; + + @JsonProperty({clazz: State}) + currState: State; + + unrollRingData: object; + imageNotes: string; + + // This empty constructor with superficial definitions + // are needed bacause JsonProperty.getJsonProperty won't + // see the properties unless defined + constructor() { + this.id = ''; + this.ebtsImageId = ''; + this.unrollImageId = ''; + this.imageData = ''; + this.unrollImageData = ''; + this.eyeLabel = EyeLabel['']; + this.contactType = ContactType['']; + this.qualityScore = 0; + this.annotations = []; + this.pastStates = []; + this.currState = new State(); + this.unrollRingData = {}; + this.imageNotes = ''; + this.dme = ''; + } +} diff --git a/web/client/src/app/types/linked-map.ts b/web/client/src/app/types/linked-map.ts new file mode 100644 index 0000000..b7a353d --- /dev/null +++ b/web/client/src/app/types/linked-map.ts @@ -0,0 +1,301 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { JsonProperty } from '../utils/decorators/json-property'; + +export class Node { + @JsonProperty({clazz: Node}) + prev: Node; + + @JsonProperty({clazz: Node}) + next: Node; + key: string; + payload: T; + + constructor(key?: string, payload?: T) { + this.prev = null; + this.next = null; + this.key = key || null; + this.payload = payload || null; + } +} + +export class LinkedMap { + map: any; + + @JsonProperty({clazz: Node}) + private head: Node; // Sentinel + + @JsonProperty({clazz: Node}) + private tail: Node; // Sentinel + public size: number; // 'length' is a reserved word for Object type + + // Accepts list of form [{ key, value(typeof T) }] + constructor(items?: Array) { + this.map = {}; + this.head = new Node(); + this.tail = new Node(); + this.size = 0; + + this.head.next = this.tail; + this.tail.prev = this.head; + + if (items && items.length) { + for (const item of items) { + this.insert(item.key, item.value); + } + } + } + + get(thing: string | number): any { + let item; + if (typeof thing === 'string') { + item = this.map[thing]; + } else if (typeof thing === 'number') { + item = this.getNode(thing); + } + return item ? item.payload : null; + } + + getFirst() { + return this.head.next == this.tail ? null : this.head.next.payload; + } + + getLast() { + return this.tail.prev == this.head ? null : this.tail.prev.payload; + } + + getNext(key: string) { + const item = this.map[key]; + if (!item || item.next == this.tail) { + return null; + } + return item.next.payload; + } + + getPrev(key: string) { + const item = this.map[key]; + if (!item || item.prev == this.head) { + return null; + } + return item.prev.payload; + } + + isEmpty(): boolean { + return this.size === 0; + } + + getIndex(key): number { + let item = this.head; + let i = 0; + + while (i <= this.size) { + if (item.key === key) { + return i - 1; + } + item = item.next; + i++; + } + + return -1; + } + + insert(key: string, payload: T, index?: number) { + let item; + if (index != undefined && + index >= 0 && + index < this.size) { + item = this.insertAtIndex(key, payload, index); + } else { + item = this.push(key, payload); + } + this.map[key] = item; + this.size += 1; + } + + insertBefore(beforeKey: string, key: string, payload: T) { + const before = this.map[beforeKey]; + let item; + if (!before) { + item = this.push(key, payload); + } else { + item = new Node(key, payload); + item.prev = before.prev; + item.next = before; + before.prev.next = item; + before.prev = item; + } + this.map[key] = item; + this.size += 1; + } + + insertAfter(afterKey: string, key: string, payload: T) { + const after = this.map[afterKey]; + let item; + if (!after) { + item = this.push(key, payload); + } else { + item = new Node(key, payload); + item.prev = after; + item.next = after.next; + after.next.prev = item; + after.next = item; + } + this.map[key] = item; + this.size += 1; + } + + compareIndices(key1: string, key2: string): number { + // return -1 if key1 comes before key2 + // return 0 if either key1 or key2 is not in map + // return 1 if key1 comes after key2 + + if (!(this.map[key1] && this.map[key2])) { + return 0; + } + + let item = this.head; + let i = 0; + + while (i <= this.size) { + if (item.key === key1) { + return -1; + } else if (item.key === key2) { + return 1; + } + + item = item.next; + i++; + } + return 0; + } + + remove(thing: string | number): any { + let item; + if (typeof thing === 'string') { + item = this.map[thing]; + } else if (typeof thing === 'number') { + // First make sure that the index is valid + if (thing < 0 || thing > this.size) { + return; + } + item = this.getNode(+thing); + } + + if (item) { + this.removeItem(item); + this.size -= 1; + return item.payload; + } + return null; + } + + clearAll() { + this.map = {}; + this.size = 0; + + this.head.next = this.tail; + this.tail.prev = this.head; + } + + toList(): Array { + const list = []; + let item = this.head; + + while (item.next != this.tail) { + item = item.next; + list.push(item.payload); + } + + return list; + } + + + /* + * For ES5, for..of only works for arrays and strings. + * To bypass this, we set `"downlevelIteration": true` and + * polyfill Symbol.iterator. + * + * Refer to: + * https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html + * https://github.com/Microsoft/TypeScript/issues/4031 + * ttps://stackoverflow.com/questions/48590262/typescript-symbol-iterator + */ + [Symbol.iterator]() { + let item = this.head; + const self = this; + + return { + next() { + item = item.next; + if (item == self.tail) { + return {value: undefined, done: true}; + } else { + return {value: item.payload, done: false}; + } + } + }; + } + + private insertAtIndex(key: string, payload: T, index: number): Node { + const old = this.getNode(index); + let item; + + if (old) { + item = new Node(key, payload); + + item.prev = old.prev; + item.next = old; + old.prev.next = item; + old.prev = item; + } else { + item = this.push(key, payload); + } + + return item; + } + + private push(key: string, payload: T): Node { + const item = new Node(key, payload); + + item.prev = this.tail.prev; + item.next = this.tail; + this.tail.prev.next = item; + this.tail.prev = item; + + return item; + } + + private removeItem(item: Node) { + if (item.prev) { + item.prev.next = item.next; + } + if (item.next) { + item.next.prev = item.prev; + } + + delete this.map[item.key]; + } + + private getNode(index: number) { + if (index < 0 || index >= this.size) { + return null; + } + + let curr = this.head; + let i = 0; + + while (i <= index) { + curr = curr.next; + i++; + } + return curr; + } +} diff --git a/web/client/src/app/types/logonInfo.ts b/web/client/src/app/types/logonInfo.ts new file mode 100644 index 0000000..50b5135 --- /dev/null +++ b/web/client/src/app/types/logonInfo.ts @@ -0,0 +1,28 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { Observable } from "rxjs"; + +@Injectable({ + providedIn: 'root' +}) +export class LogonInfo { + + loginUrl = '/api/logon/info'; + + constructor(private http: HttpClient) { + } + + getIsSecure(): Observable { + return this.http.get(this.loginUrl); + } +} diff --git a/web/client/src/app/types/notification.ts b/web/client/src/app/types/notification.ts new file mode 100644 index 0000000..ccb3a8a --- /dev/null +++ b/web/client/src/app/types/notification.ts @@ -0,0 +1,36 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { uuid } from 'short-uuid'; + +export class AppNotification { + id: string; + message: string; + icon: string; + action: string; + userId: number; + dateCreated: Date; + isNew: boolean; + + constructor( + message: string, + icon: string, + action: string, + userId: number + ) { + this.id = uuid(); + this.message = message; + this.icon = icon; + this.action = action; + this.userId = userId; + this.dateCreated = new Date(); + this.isNew = true; + } +} diff --git a/web/client/src/app/types/person.ts b/web/client/src/app/types/person.ts new file mode 100644 index 0000000..ebe0a33 --- /dev/null +++ b/web/client/src/app/types/person.ts @@ -0,0 +1,29 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ +export class Person { + + id: string; + name: string; + title: string; + department: string; + address: string; + email: string; + phone: string; + + constructor() { + this.id = ''; + this.name = ''; + this.title = ''; + this.department = ''; + this.address = ''; + this.email = ''; + this.phone = ''; + } +} diff --git a/web/client/src/app/types/probe.ts b/web/client/src/app/types/probe.ts new file mode 100644 index 0000000..2779bb1 --- /dev/null +++ b/web/client/src/app/types/probe.ts @@ -0,0 +1,32 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { EbtsImageData } from './image-data'; +import { JsonProperty } from '../utils/decorators/json-property'; + +export class Probe { + id: string; + name: string; + subjectIdentifier: string; + transactionControlReference: string; + transactionControlNumber: string; + + @JsonProperty({clazz: EbtsImageData}) + imageList: EbtsImageData[]; + + constructor() { + this.id = undefined; + this.name = undefined; + this.subjectIdentifier = undefined; + this.transactionControlReference = undefined; + this.transactionControlNumber = undefined; + this.imageList = []; + } +} diff --git a/web/client/src/app/types/report-data.ts b/web/client/src/app/types/report-data.ts new file mode 100644 index 0000000..0dbcf6d --- /dev/null +++ b/web/client/src/app/types/report-data.ts @@ -0,0 +1,71 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Probe } from './probe'; +import { Candidate } from './candidate'; +import { CaseInformation } from './case-information'; +import { JsonProperty } from '../utils/decorators/json-property'; +import { Profile } from '../profile/profile'; + +export enum ResponseType { + SRB = 1, + ERRB = 2, + XML = 3, + Image = 4, + Unknown = 0 +} + +export class ReportData { + id: string; + fileName: string; + allowNullRequester: boolean; + + @JsonProperty({clazz: Probe}) + probe: Probe; + + @JsonProperty({clazz: Candidate}) + candidates: Candidate[]; + + includeExclude: boolean; + responseType: ResponseType; + + @JsonProperty({clazz: CaseInformation}) + caseInformation: CaseInformation; + + userId: string; + isFinished: boolean; + errorMessages: string[]; + invalidFields: string[]; + + workedBy: UserAuthentication; + + constructor() { + this.id = ''; + this.fileName = ''; + this.probe = new Probe(); + this.candidates = []; + this.includeExclude = false; + this.responseType = ResponseType.Unknown; + this.caseInformation = new CaseInformation(); + this.userId = ''; + this.isFinished = false; + this.errorMessages = Array(); + this.invalidFields = Array(); + this.workedBy = new UserAuthentication(); + this.allowNullRequester = false; + } +} + +export class UserAuthentication { + profile: Profile; +} + + + diff --git a/web/client/src/app/types/service-response.ts b/web/client/src/app/types/service-response.ts new file mode 100644 index 0000000..2536796 --- /dev/null +++ b/web/client/src/app/types/service-response.ts @@ -0,0 +1,39 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +export enum ServiceStatus { + NotSent = 'NotSent', + Pending = 'Pending', + Error = 'Error', + Received = 'Received' +} + +export class ServiceResponse { + // imageId is the modified id number + // imageId and ebtsImageId can be the same number it's not a resubmitted image. + imageId = ''; + error = ''; + status: ServiceStatus; + ebtsImageId = ''; + isResubmit = false; + + constructor( + imageId: string, + error: string, + status: ServiceStatus, + ebtsImageId: string, + isResubmit: boolean) { + this.imageId = imageId; + this.error = error; + this.status = status; + this.ebtsImageId = ebtsImageId; + this.isResubmit = isResubmit; + } +} diff --git a/web/client/src/app/types/service-status.ts b/web/client/src/app/types/service-status.ts new file mode 100644 index 0000000..6f87671 --- /dev/null +++ b/web/client/src/app/types/service-status.ts @@ -0,0 +1,49 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +export interface IwpServiceStatus { + name: ServiceStatusNameType; + displayName: string; + status: ServiceStatusStatusType; + consumerCount: string; + enqueueCount: string; + dequeueCount: string; + listenerType: string; + statusHint: string; + serviceDefinition: string; + version: string; +} + +export interface IwpServiceProperties { + serviceName: string; + serviceEnabled: boolean; + serviceRequest: string; + serviceResponse: string; + serviceExcluded: boolean; +} + +export enum ServiceStatusStatusType { + Loading = 'Loading', + Online = 'Online', + Disabled = 'Disabled', + Error = 'Error' +} + +export enum ServiceStatusNameType { + acii = 'acii', + annotation = 'annotation', + biqt = 'biqt', + contactdetection = 'contactdetection', + tshepii = 'tshepii', + dualpdm = 'dualpdm', + pdm = 'pdm', + webbackend = 'webbackend', + webfrontend = 'webfrontend' +} diff --git a/web/client/src/app/types/state.ts b/web/client/src/app/types/state.ts new file mode 100644 index 0000000..6465eda --- /dev/null +++ b/web/client/src/app/types/state.ts @@ -0,0 +1,39 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Annotation } from './annotation'; +import { JsonProperty } from '../utils/decorators/json-property'; +import { ComponentRef } from '@angular/core'; + +export enum StateType { + ADD, + REMOVE, + BULK_REMOVE, + RESET, + CLEAR, + GROUP_ONLY, + GROUP_ADD +} + +export class State { + @JsonProperty({clazz: Annotation}) + annotation: Annotation; + data: any; // This feels like a hack + type: StateType; + compRef: ComponentRef; + + constructor(); + constructor(annotation: Annotation, type: StateType); + constructor(annotation?: Annotation, type?: StateType) { + this.annotation = annotation; + this.data = undefined; + this.type = type; + } +} diff --git a/web/client/src/app/types/versionInfo.ts b/web/client/src/app/types/versionInfo.ts new file mode 100644 index 0000000..901b798 --- /dev/null +++ b/web/client/src/app/types/versionInfo.ts @@ -0,0 +1,21 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +export class VersionInfo { + iwpVersion: string; + uiVersion: string; + serviceVersions: Map = new Map(); + + constructor() { + this.iwpVersion = ''; + this.uiVersion = ''; + this.serviceVersions = new Map(); + } +} diff --git a/web/client/src/app/user/user.component.html b/web/client/src/app/user/user.component.html new file mode 100644 index 0000000..b2fab7f --- /dev/null +++ b/web/client/src/app/user/user.component.html @@ -0,0 +1,231 @@ + +
+
+ + + + + +
+ + +
Agency Information:
+
+ + Destination Agency Identifier + + +
+ ERROR! + The destination agency identifier must be 9 alphanumeric characters +
+ + Originating Agency Identifier + + +
+ ERROR! + The originating agency identifier must be 9 alphanumeric characters +
+ + Attention Indicator + + +
+ ERROR! + A transaction must have an attention indicator and it must not contain a '.' +
+ + Source Agency + + +
+ ERROR! + A source agency must be 9 alphanumeric characters +
+
+ Exclude from Report? + +
+
+ + + +
+
Personal Information:
+
+ + Full Name + + +
Entry must be 2-40 alpha characters. +
+ + Email + + +
The email is invalid. Example email: email@example.com +
+ + Phone Number + + +
The entry is invalid. Use the format: xxx-xxx-xxxx +
+ + Title + + +
Entry must be less than 40 characters. +
+ + Department + + +
Entry must be less than 40 characters. +
+ + Address + + +
Entry must be less than 100 characters. +
+
+ + + +
+
Annotations:
+
+ + Stroke Width + + + Stroke width must be between 1 & 10 + + +
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+ + + +
+ + Number of History Files + + +
+

+
+ + +
+
+
+ + + +
+
Password:
+
+ + + Incorrect password + + + + + + New password must be between 8 & 64 characters. + + + + + + Passwords do not match + + +
+
+
+ +
+
+
+
+
+
+
diff --git a/web/client/src/app/user/user.component.ts b/web/client/src/app/user/user.component.ts new file mode 100644 index 0000000..05ca3cc --- /dev/null +++ b/web/client/src/app/user/user.component.ts @@ -0,0 +1,142 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component, OnInit, OnChanges } from '@angular/core'; +import { ProfileService } from '../profile/profile.service'; +import { Profile } from '../profile/profile'; +import { UntypedFormGroup } from '@angular/forms'; +import { ViewChild } from '@angular/core'; +import { NgForm } from '@angular/forms'; +import * as cloneDeep from 'lodash/cloneDeep'; +import { AdminService } from '../admin/admin.service'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + templateUrl: 'user.component.html', + styles: [` + #profile-tab .form-control, #password-tab .form-control { + width: 200px; + } + + .mat-mdc-form-field { + padding-bottom: 6px; + } + + .container { + max-width: 760px; + } + + `] +}) +export class UserComponent implements OnInit, OnChanges { + initialProfile: Profile; + currentProfile: Profile = this.profileService.createEmptyProfile(); + currentPassword = ''; + newPassword = ''; + confirmPassword = ''; + + passwordVisible = false; + + canEdit = false; + numHistory = [5, 10, 15, 20]; + privs: any = []; + formGroup: UntypedFormGroup; + + @ViewChild('editForm', {static: true}) + private editForm: NgForm; + + //passwordForm + @ViewChild('passwordForm', {static: true}) + private passwordForm: NgForm; + + constructor(private profileService: ProfileService, + private adminService: AdminService, + private matSnackbar: MatSnackBar) { + this.profileService.currentProfile$ + .subscribe((profile) => { + this.currentProfile = profile; + }); + this.profileService.getUserPrivileges().subscribe(privs => { + this.privs = Object.keys(privs); + }); + } + + ngOnInit() { + this.profileService.getProfile().subscribe(prfs => { + // Do nothing. simply a session alive check + }); + this.currentProfile = this.profileService.getCurrentProfile(); + this.initialProfile = cloneDeep(this.currentProfile); + } + + ngOnChanges(changes) { + } + + saveProfile() { + if (this.canEdit) { + this.profileService.updateProfile(this.currentProfile); + + } else { + if (this.initialProfile.id == null) { + this.initialProfile = cloneDeep(this.currentProfile); + } + } + + this.canEdit = !this.canEdit; + } + + isProfileValid() { + let temp = true; + + Object.keys(this.editForm.controls).forEach((key: string) => { + const abstractControl = this.editForm.controls[key]; + + if (abstractControl.invalid) { + temp = false; + } + }); + + return temp; + } + + resetProfile() { + this.currentProfile = cloneDeep(this.initialProfile); + } + + updatePassword(passwordFormGroup: NgForm) { + // Check to see if the currentPassword input is correct. + this.adminService.checkPassword(this.currentProfile.name, this.currentPassword).subscribe({ + next: (resp) => { + // If currentPassword input is correct, try to update the user in the next handler + // If it's wrong, it does NOT update the user and notifies the user that the currentPassword input was wrong + this.adminService.updatePassword(this.currentProfile.name, this.currentPassword, this.newPassword).subscribe({ + next: (resp) => { + //reset the create modal + this.currentPassword = ''; + this.newPassword = ''; + this.confirmPassword = ''; + this.matSnackbar.open('User password updated', 'Dismiss'); + }, + error: (err) => { + this.matSnackbar.open('User password was not Updated!', 'Dismiss'); + } + }); + }, + error: (err) => { + passwordFormGroup.form.get('currentPasswordInput').setErrors({badPassword: true}); + this.matSnackbar.open('Password incorrect; user was not updated!'); + } + }); + } + + showHidePassword() { + this.passwordVisible = !this.passwordVisible; + } +} diff --git a/web/client/src/app/user/user.ts b/web/client/src/app/user/user.ts new file mode 100644 index 0000000..7486ecd --- /dev/null +++ b/web/client/src/app/user/user.ts @@ -0,0 +1,26 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Profile } from '../profile/profile'; + +export class User { + id = -1; + username: string; + password: string; + roles: string[] = []; + profile: Profile; + isNewUser: boolean; + + constructor(id, username, profile) { + this.id = id; + this.username = username; + this.profile = profile; + } +} diff --git a/web/client/src/app/utils/content.service.ts b/web/client/src/app/utils/content.service.ts new file mode 100644 index 0000000..8f51522 --- /dev/null +++ b/web/client/src/app/utils/content.service.ts @@ -0,0 +1,41 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; + +@Injectable() +export class ContentService { + + _data = {}; + protected _config: any = {}; + + constructor() { + } + + getContent(key) { + return this._data[key]; + } + + setContent(key, value) { + this._data[key] = value; + } + + get config() { + return this._config; + }; + + configure(config) { + this._config = Object.assign(config, this._config); + } + + getToolConfig(toolName) { + return this._config.tools.find((x) => x.name == toolName); + } +} diff --git a/web/client/src/app/utils/debounce.ts b/web/client/src/app/utils/debounce.ts new file mode 100644 index 0000000..94be134 --- /dev/null +++ b/web/client/src/app/utils/debounce.ts @@ -0,0 +1,59 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +/** + * Utility function to delay (or debounce) the firings of callback handlers during persistent identical events + * + * @param func - callback function to fire once the "debounce period" is over (twin event has no fired X amount of time) + * @param wait - the "debounce period" in ms + * @param immediate - if true, fires callback first, then debounces sequential events until "debounce period" expires + * @returns + * @private + */ +// taken from https://github.com/component/debounce/blob/master/index.js +export const debounce = (func: () => void, wait?: number, immediate?: boolean) => { + let timestamp; let timeout; let result; let context; let args; + + if (wait == null) { + wait = 100; +} + + const delay = function() { + const last = Date.now() - timestamp; + + if (last < wait && last >= 0) { + timeout = setTimeout(delay, wait - last); + } else { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + context = args = null; + } + } + }; + + const d = function() { + context = this; + args = arguments; + timestamp = Date.now(); + const callNow = immediate && !timeout; + if (!timeout) { +timeout = setTimeout(delay, wait); +} + if (callNow) { + result = func.apply(context, args); + context = args = null; + } + + return result; + }; + + return d; +}; diff --git a/web/client/src/app/utils/decorators/json-property.ts b/web/client/src/app/utils/decorators/json-property.ts new file mode 100644 index 0000000..f0d52f3 --- /dev/null +++ b/web/client/src/app/utils/decorators/json-property.ts @@ -0,0 +1,50 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import 'reflect-metadata'; + +const JSON_METADATA_KEY = 'jsonProperty'; + +// Taken from http://cloudmark.github.io/Json-Mapping/ +interface IJsonMetaData { + name?: string; + clazz?: new() => T; +} + +function JsonProperty(metadata?: IJsonMetaData | string): any { + if (metadata instanceof String || typeof metadata === 'string') { + return Reflect.metadata(JSON_METADATA_KEY, { + name: metadata, + clazz: undefined + }); + } + + const metadataObj = >metadata; + return Reflect.metadata(JSON_METADATA_KEY, { + name: metadataObj ? metadataObj.name : undefined, + clazz: metadataObj ? metadataObj.clazz : undefined + }); +} + +function getClazz(target: any, propertyKey: string): any { + return Reflect.getMetadata('design:type', target, propertyKey); +} + +function getJsonProperty(target: any, propertyKey: string): IJsonMetaData { + return Reflect.getMetadata(JSON_METADATA_KEY, target, propertyKey); +} + +export { + JSON_METADATA_KEY, + IJsonMetaData, + JsonProperty, + getClazz, + getJsonProperty +}; diff --git a/web/client/src/app/utils/dialog.serivce.ts b/web/client/src/app/utils/dialog.serivce.ts new file mode 100644 index 0000000..a138a6f --- /dev/null +++ b/web/client/src/app/utils/dialog.serivce.ts @@ -0,0 +1,25 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; +import { Observable, of } from 'rxjs'; + +@Injectable() +export class DialogService { + confirm(message?: string): Observable { + const confirmation = window.confirm(message || 'Is it OK?'); + + return of(confirmation); + } + + confirmBooleam(message?: string) { + return window.confirm(message || 'Is it OK?'); + } +} diff --git a/web/client/src/app/utils/let.directive.ts b/web/client/src/app/utils/let.directive.ts new file mode 100644 index 0000000..27bc239 --- /dev/null +++ b/web/client/src/app/utils/let.directive.ts @@ -0,0 +1,33 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; + +interface LetContext { + myLet: T; +} + +@Directive({ + selector: '[myLet]' +}) + +export class LetDirective { + private _context: LetContext = {myLet: null}; + + constructor(_viewContainer: ViewContainerRef, + _templateRef: TemplateRef>) { + _viewContainer.createEmbeddedView(_templateRef, this._context); + } + + @Input() + set myLet(value: T) { + this._context.myLet = value; + } +} diff --git a/web/client/src/app/utils/map-utils.ts b/web/client/src/app/utils/map-utils.ts new file mode 100644 index 0000000..485a61b --- /dev/null +++ b/web/client/src/app/utils/map-utils.ts @@ -0,0 +1,78 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import 'reflect-metadata'; + +import { getJsonProperty, getClazz } from './decorators/json-property'; + +export class MapUtils { + static isPrimitive(obj) { + switch (typeof obj) { + case 'string': + case 'number': + case 'boolean': + return true; + } + return !!(obj instanceof String || obj === String || + obj instanceof Number || obj === Number || + obj instanceof Boolean || obj === Boolean); + } + + static isArray(obj) { + if (obj === Array) { + return true; + } else if (typeof Array.isArray === 'function') { + return Array.isArray(obj); + } + return !!(obj instanceof Array); + } + + static deserialize(clazz: new() => T, jsonObject) { + if (clazz === undefined || jsonObject === undefined) { + return undefined; + } + const obj = new clazz(); + + Object.keys(obj).forEach(key => { + const propertyMetadataFn: (IJsonMetadata) => any = (propertyMetadata) => { + const propertyName = propertyMetadata.name || key; + const innerJson = jsonObject ? jsonObject[propertyName] : undefined; + + const clz = getClazz(obj, key); + + if (MapUtils.isArray(clz)) { + const metadata = getJsonProperty(obj, key); + if (metadata.clazz || MapUtils.isPrimitive(clz)) { + if (innerJson && MapUtils.isArray(innerJson)) { + return innerJson.map(item => MapUtils.deserialize(metadata.clazz, item)); + } else { + return undefined; + } + } else { + return innerJson; + } + } else if (!MapUtils.isPrimitive(clz)) { + return MapUtils.deserialize(clz, innerJson); + } + return jsonObject ? jsonObject[propertyName] : undefined; + }; + + const pMD = getJsonProperty(obj, key); + + if (pMD) { + obj[key] = propertyMetadataFn(pMD); + } else if (jsonObject && jsonObject[key] !== undefined) { + obj[key] = jsonObject[key]; + } + }); + + return obj; + } +} diff --git a/web/client/src/app/utils/paper-canvas.component.ts b/web/client/src/app/utils/paper-canvas.component.ts new file mode 100644 index 0000000..5437803 --- /dev/null +++ b/web/client/src/app/utils/paper-canvas.component.ts @@ -0,0 +1,37 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Component, AfterViewInit, ViewChild, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'app-paper-canvas', + template: ` + `, + styles: [':host { display: block; }', ':host[hidden=""] { display: none !important; }', 'canvas { text-align: left; }', + ':host.show-boundary { border: 1px solid #dddddd; background-color: #f5f5f5; background-image: linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%); }'] +}) +export class PaperCanvasComponent implements AfterViewInit { + @Input() width; + @Input() height; + @ViewChild('canvas', {static: true}) canvas; + @Output() canvasInit: EventEmitter = new EventEmitter(true); + + constructor() { + } + + ngAfterViewInit() { + this.canvasInit.emit(this.canvas); + } + + // this method is purposely a no-op + // listening to click events on the canvas element forces Angular's change detection to fire when using canvas tools + onCanvasClick() { + } +} diff --git a/web/client/src/app/utils/paper.service.ts b/web/client/src/app/utils/paper.service.ts new file mode 100644 index 0000000..3af1ae7 --- /dev/null +++ b/web/client/src/app/utils/paper.service.ts @@ -0,0 +1,75 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { PaperScope, Project, Size } from 'paper'; +import { debounce } from './debounce'; + + +@Injectable() +export class Paper { + scope = new PaperScope(); + projects: InstanceType[] = []; + private _projectsById: object = {}; + + constructor() { + this.scope.activate(); + } + + addCanvas(canvas: HTMLCanvasElement, uuid?: string, isVertical?: BehaviorSubject, resize: boolean = false) { + let p: InstanceType = this._projectsById[uuid] || null; + + if (p == null) { + canvas.setAttribute('hidpi', 'off'); + p = new Project(canvas); + this.projects.push(p); + if (!!uuid) { + this._projectsById[uuid] = p; + } + } + + p.activate(); + + if (isVertical) { + isVertical.subscribe(vert => { + debounce(() => { + this.resize(p); + }, 125)(); + }); + } + + if (resize) { + // resize the canvas window when the browser window is resized + window.addEventListener('resize', debounce(() => { + this.resize(p); + }, 125), false); + } + + return p; + } + + resize(p: InstanceType) { + const bounds = (p.view.element.parentNode).getBoundingClientRect(); + + if (bounds.width == 0 || bounds.height == 0) { + for (const project of this.projects) { + const projBounds = project.view.bounds; + + if (projBounds.height > 0 && projBounds.width > 0) { + p.view.viewSize = new Size(projBounds.width, p.view.viewSize.height); + return; + } + } + } + + p.view.viewSize = new Size(bounds.width, p.view.viewSize.height); + } +} diff --git a/web/client/src/app/utils/utils.module.ts b/web/client/src/app/utils/utils.module.ts new file mode 100644 index 0000000..a742b6a --- /dev/null +++ b/web/client/src/app/utils/utils.module.ts @@ -0,0 +1,46 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { NgModule } from '@angular/core'; +import { ModalModule } from 'ngx-bootstrap/modal'; +import { TooltipModule } from 'ngx-bootstrap/tooltip'; +import { PaperCanvasComponent } from './paper-canvas.component'; +import { LetDirective } from './let.directive'; +import { MinDirective } from './validators/min.directive'; +import { MaxDirective } from './validators/max.directive'; +import { PasswordMatcherDirective } from './validators/password-matcher.directive'; + +/** + * Feature NgModule to easily pass all Utilities (within src/app/utils) to other Feature Modules + */ + +@NgModule({ + imports: [ + ModalModule.forRoot(), + TooltipModule.forRoot(),], + declarations: [ + PaperCanvasComponent, + LetDirective, + MinDirective, + MaxDirective, + PasswordMatcherDirective, + ], + exports: [ + ModalModule, + TooltipModule, + PaperCanvasComponent, + LetDirective, + MinDirective, + MaxDirective, + PasswordMatcherDirective, + ] +}) +export class UtilsModule { +} diff --git a/web/client/src/app/utils/validators/max.directive.spec.ts b/web/client/src/app/utils/validators/max.directive.spec.ts new file mode 100644 index 0000000..a1c3f69 --- /dev/null +++ b/web/client/src/app/utils/validators/max.directive.spec.ts @@ -0,0 +1,25 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { inject, TestBed } from '@angular/core/testing'; +import { MaxDirective } from './max.directive'; + +describe('XXIX. The MaxDirective Class:', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [MaxDirective] + }); + }); + + it('a. maxValidator takes zero parameters and returns an object instance of the ValidatorFn interface (i.e., a function).', inject([MaxDirective], (service: MaxDirective) => { + const res = service.maxValidator(); + expect(res).toBeInstanceOf(Function); + })); +}); diff --git a/web/client/src/app/utils/validators/max.directive.ts b/web/client/src/app/utils/validators/max.directive.ts new file mode 100644 index 0000000..cf439e3 --- /dev/null +++ b/web/client/src/app/utils/validators/max.directive.ts @@ -0,0 +1,34 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Directive, Input } from '@angular/core'; +import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn } from '@angular/forms'; + +@Directive({ + selector: '[appMax]', + providers: [{provide: NG_VALIDATORS, useExisting: MaxDirective, multi: true}] +}) +export class MaxDirective implements Validator { + @Input('appMax') max: number; + + validate(control: AbstractControl): ValidationErrors | null { + return this.max ? this.maxValidator()(control) : null; + } + + maxValidator(): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + const invalid = this.max < control.value; + return invalid ? {invalidMax: {value: control.value}} : null; + }; + } + + constructor() { + } +} diff --git a/web/client/src/app/utils/validators/min.directive.spec.ts b/web/client/src/app/utils/validators/min.directive.spec.ts new file mode 100644 index 0000000..c35547a --- /dev/null +++ b/web/client/src/app/utils/validators/min.directive.spec.ts @@ -0,0 +1,25 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { MinDirective } from './min.directive'; +import { inject, TestBed } from '@angular/core/testing'; + +describe('XXX. The MaxDirective Class:', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [MinDirective] + }); + }); + + it('a. minValidator takes zero parameters and returns an object instance of the ValidatorFn interface (i.e., a function).', inject([MinDirective], (service: MinDirective) => { + const res = service.minValidator(); + expect(res).toBeInstanceOf(Function); + })); +}); diff --git a/web/client/src/app/utils/validators/min.directive.ts b/web/client/src/app/utils/validators/min.directive.ts new file mode 100644 index 0000000..8bd6668 --- /dev/null +++ b/web/client/src/app/utils/validators/min.directive.ts @@ -0,0 +1,35 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Directive, Input } from '@angular/core'; +import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn } from '@angular/forms'; + +@Directive({ + selector: '[appMin]', + providers: [{provide: NG_VALIDATORS, useExisting: MinDirective, multi: true}] +}) +export class MinDirective implements Validator { + @Input('appMin') min: number; + + validate(control: AbstractControl): ValidationErrors | null { + return this.min ? this.minValidator()(control) : null; + } + + minValidator(): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + const invalid = this.min > control.value; + return invalid ? {invalidMin: {value: control.value}} : null; + }; + } + + constructor() { + } + +} diff --git a/web/client/src/app/utils/validators/password-matcher.directive.spec.ts b/web/client/src/app/utils/validators/password-matcher.directive.spec.ts new file mode 100644 index 0000000..aa43a82 --- /dev/null +++ b/web/client/src/app/utils/validators/password-matcher.directive.spec.ts @@ -0,0 +1,18 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { PasswordMatcherDirective } from './password-matcher.directive'; + +describe('XXXI. The PasswordMatcherDirective Class:', () => { + it('a. should create an instance of the class', () => { + const directive = new PasswordMatcherDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/web/client/src/app/utils/validators/password-matcher.directive.ts b/web/client/src/app/utils/validators/password-matcher.directive.ts new file mode 100644 index 0000000..88ed2a1 --- /dev/null +++ b/web/client/src/app/utils/validators/password-matcher.directive.ts @@ -0,0 +1,57 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +import { Directive, Input } from '@angular/core'; +import { + UntypedFormGroup, + NG_VALIDATORS, + ValidationErrors, + Validator, + ValidatorFn + } from '@angular/forms'; + +@Directive({ + selector: '[appPasswordMatcher]', + providers: [{provide: NG_VALIDATORS, useExisting: PasswordMatcherDirective, multi: true}] +}) + +// Input Trial +export class PasswordMatcherDirective implements Validator { + @Input() password1: string; + @Input() password2: string; + + validate(formGroup: UntypedFormGroup): ValidationErrors { + return this.passwordMatcherValidator()(formGroup)!; + } + + passwordMatcherValidator(): ValidatorFn { + return (formGroup: UntypedFormGroup): ValidationErrors | null => { + const newPassword = formGroup.get(this.password1); + const confirmPassword = formGroup.get(this.password2); + + if (newPassword != null && confirmPassword != null) { + const misMatched = newPassword.value !== confirmPassword.value; + if (misMatched) { + confirmPassword.setErrors({passwordMismatch: true}); + } else { + confirmPassword.setErrors(null); + } + return misMatched ? {passwordMismatch: true} : null; + } else { + return null; + } + }; + } + + constructor() { + + } +} + diff --git a/web/client/src/css/dropzone.css b/web/client/src/css/dropzone.css new file mode 100644 index 0000000..1dd8729 --- /dev/null +++ b/web/client/src/css/dropzone.css @@ -0,0 +1,509 @@ +/* + * The MIT License + * Copyright (c) 2012 Matias Meno + */ + +@-webkit-keyframes passing-through { + 0% { + opacity: 0; + -webkit-transform: translateY(40px); + -moz-transform: translateY(40px); + -ms-transform: translateY(40px); + -o-transform: translateY(40px); + transform: translateY(40px); + } + 30%, 70% { + opacity: 1; + -webkit-transform: translateY(0px); + -moz-transform: translateY(0px); + -ms-transform: translateY(0px); + -o-transform: translateY(0px); + transform: translateY(0px); + } + 100% { + opacity: 0; + -webkit-transform: translateY(-40px); + -moz-transform: translateY(-40px); + -ms-transform: translateY(-40px); + -o-transform: translateY(-40px); + transform: translateY(-40px); + } +} + +@-moz-keyframes passing-through { + 0% { + opacity: 0; + -webkit-transform: translateY(40px); + -moz-transform: translateY(40px); + -ms-transform: translateY(40px); + -o-transform: translateY(40px); + transform: translateY(40px); + } + 30%, 70% { + opacity: 1; + -webkit-transform: translateY(0px); + -moz-transform: translateY(0px); + -ms-transform: translateY(0px); + -o-transform: translateY(0px); + transform: translateY(0px); + } + 100% { + opacity: 0; + -webkit-transform: translateY(-40px); + -moz-transform: translateY(-40px); + -ms-transform: translateY(-40px); + -o-transform: translateY(-40px); + transform: translateY(-40px); + } +} + +@keyframes passing-through { + 0% { + opacity: 0; + -webkit-transform: translateY(40px); + -moz-transform: translateY(40px); + -ms-transform: translateY(40px); + -o-transform: translateY(40px); + transform: translateY(40px); + } + 30%, 70% { + opacity: 1; + -webkit-transform: translateY(0px); + -moz-transform: translateY(0px); + -ms-transform: translateY(0px); + -o-transform: translateY(0px); + transform: translateY(0px); + } + 100% { + opacity: 0; + -webkit-transform: translateY(-40px); + -moz-transform: translateY(-40px); + -ms-transform: translateY(-40px); + -o-transform: translateY(-40px); + transform: translateY(-40px); + } +} + +@-webkit-keyframes slide-in { + 0% { + opacity: 0; + -webkit-transform: translateY(40px); + -moz-transform: translateY(40px); + -ms-transform: translateY(40px); + -o-transform: translateY(40px); + transform: translateY(40px); + } + 30% { + opacity: 1; + -webkit-transform: translateY(0px); + -moz-transform: translateY(0px); + -ms-transform: translateY(0px); + -o-transform: translateY(0px); + transform: translateY(0px); + } +} + +@-moz-keyframes slide-in { + 0% { + opacity: 0; + -webkit-transform: translateY(40px); + -moz-transform: translateY(40px); + -ms-transform: translateY(40px); + -o-transform: translateY(40px); + transform: translateY(40px); + } + 30% { + opacity: 1; + -webkit-transform: translateY(0px); + -moz-transform: translateY(0px); + -ms-transform: translateY(0px); + -o-transform: translateY(0px); + transform: translateY(0px); + } +} + +@keyframes slide-in { + 0% { + opacity: 0; + -webkit-transform: translateY(40px); + -moz-transform: translateY(40px); + -ms-transform: translateY(40px); + -o-transform: translateY(40px); + transform: translateY(40px); + } + 30% { + opacity: 1; + -webkit-transform: translateY(0px); + -moz-transform: translateY(0px); + -ms-transform: translateY(0px); + -o-transform: translateY(0px); + transform: translateY(0px); + } +} + +@-webkit-keyframes pulse { + 0% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); + } + 10% { + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -ms-transform: scale(1.1); + -o-transform: scale(1.1); + transform: scale(1.1); + } + 20% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); + } +} + +@-moz-keyframes pulse { + 0% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); + } + 10% { + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -ms-transform: scale(1.1); + -o-transform: scale(1.1); + transform: scale(1.1); + } + 20% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); + } +} + +@keyframes pulse { + 0% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); + } + 10% { + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -ms-transform: scale(1.1); + -o-transform: scale(1.1); + transform: scale(1.1); + } + 20% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); + } +} + +.dropzone, .dropzone * { + box-sizing: border-box; +} + +.dropzone { + min-height: 150px; + border: 2px solid rgba(0, 0, 0, 0.3); + background: white; + padding: 20px 20px; +} + +.dropzone.dz-clickable { + cursor: pointer; +} + +.dropzone.dz-clickable * { + cursor: default; +} + +.dropzone.dz-clickable .dz-message, .dropzone.dz-clickable .dz-message * { + cursor: pointer; +} + +.dropzone.dz-started .dz-message { + display: none; +} + +.dropzone.dz-drag-hover { + border-style: solid; +} + +.dropzone.dz-drag-hover .dz-message { + opacity: 0.5; +} + +.dropzone .dz-message { + text-align: center; + margin: 2em 0; +} + +.dropzone .dz-preview { + position: relative; + display: inline-block; + vertical-align: top; + margin: 16px; + min-height: 100px; +} + +.dropzone .dz-preview:hover { + z-index: 1000; +} + +.dropzone .dz-preview:hover .dz-details { + opacity: 1; +} + +.dropzone .dz-preview.dz-file-preview .dz-image { + border-radius: 20px; + background: #999; + background: linear-gradient(to bottom, #eee, #ddd); +} + +.dropzone .dz-preview.dz-file-preview .dz-details { + opacity: 1; +} + +.dropzone .dz-preview.dz-image-preview { + background: white; +} + +.dropzone .dz-preview.dz-image-preview .dz-details { + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -ms-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; +} + +.dropzone .dz-preview .dz-remove { + font-size: 14px; + text-align: center; + display: block; + cursor: pointer; + border: none; +} + +.dropzone .dz-preview .dz-remove:hover { + text-decoration: underline; +} + +.dropzone .dz-preview .dz-details { + z-index: 20; + position: absolute; + top: 0; + left: 0; + opacity: 0; + font-size: 13px; + min-width: 100%; + max-width: 100%; + padding: 2em 1em; + text-align: center; + color: rgba(0, 0, 0, 0.9); + line-height: 150%; +} + +.dropzone .dz-preview .dz-details .dz-size { + margin-bottom: 1em; + font-size: 16px; +} + +.dropzone .dz-preview .dz-details .dz-filename { + white-space: nowrap; +} + +.dropzone .dz-preview .dz-details .dz-filename:hover span { + border: 1px solid rgba(200, 200, 200, 0.8); + background-color: rgba(255, 255, 255, 0.8); +} + +.dropzone .dz-preview .dz-details .dz-filename:not(:hover) { + overflow: hidden; + text-overflow: ellipsis; +} + +.dropzone .dz-preview .dz-details .dz-filename:not(:hover) span { + border: 1px solid transparent; +} + +.dropzone .dz-preview .dz-details .dz-filename span, .dropzone .dz-preview .dz-details .dz-size span { + background-color: rgba(255, 255, 255, 0.4); + padding: 0 0.4em; + border-radius: 3px; +} + +.dropzone .dz-preview:hover .dz-image img { + -webkit-transform: scale(1.05, 1.05); + -moz-transform: scale(1.05, 1.05); + -ms-transform: scale(1.05, 1.05); + -o-transform: scale(1.05, 1.05); + transform: scale(1.05, 1.05); + -webkit-filter: blur(8px); + filter: blur(8px); +} + +.dropzone .dz-preview .dz-image { + border-radius: 20px; + overflow: hidden; + width: 120px; + height: 120px; + position: relative; + display: block; + z-index: 10; +} + +.dropzone .dz-preview .dz-image img { + display: block; +} + +.dropzone .dz-preview.dz-success .dz-success-mark { + -webkit-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); + -moz-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); + -ms-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); + -o-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); + animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); +} + +.dropzone .dz-preview.dz-error .dz-error-mark { + opacity: 1; + -webkit-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); + -moz-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); + -ms-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); + -o-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); + animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); +} + +.dropzone .dz-preview .dz-success-mark, .dropzone .dz-preview .dz-error-mark { + pointer-events: none; + opacity: 0; + z-index: 500; + position: absolute; + display: block; + top: 50%; + left: 50%; + margin-left: -27px; + margin-top: -27px; +} + +.dropzone .dz-preview .dz-success-mark svg, .dropzone .dz-preview .dz-error-mark svg { + display: block; + width: 54px; + height: 54px; +} + +.dropzone .dz-preview.dz-processing .dz-progress { + opacity: 1; + -webkit-transition: all 0.2s linear; + -moz-transition: all 0.2s linear; + -ms-transition: all 0.2s linear; + -o-transition: all 0.2s linear; + transition: all 0.2s linear; +} + +.dropzone .dz-preview.dz-complete .dz-progress { + opacity: 0; + -webkit-transition: opacity 0.4s ease-in; + -moz-transition: opacity 0.4s ease-in; + -ms-transition: opacity 0.4s ease-in; + -o-transition: opacity 0.4s ease-in; + transition: opacity 0.4s ease-in; +} + +.dropzone .dz-preview:not(.dz-processing) .dz-progress { + -webkit-animation: pulse 6s ease infinite; + -moz-animation: pulse 6s ease infinite; + -ms-animation: pulse 6s ease infinite; + -o-animation: pulse 6s ease infinite; + animation: pulse 6s ease infinite; +} + +.dropzone .dz-preview .dz-progress { + opacity: 1; + z-index: 1000; + pointer-events: none; + position: absolute; + height: 16px; + left: 50%; + top: 50%; + margin-top: -8px; + width: 80px; + margin-left: -40px; + background: rgba(255, 255, 255, 0.9); + -webkit-transform: scale(1); + border-radius: 8px; + overflow: hidden; +} + +.dropzone .dz-preview .dz-progress .dz-upload { + background: #333; + background: linear-gradient(to bottom, #666, #444); + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 0; + -webkit-transition: width 300ms ease-in-out; + -moz-transition: width 300ms ease-in-out; + -ms-transition: width 300ms ease-in-out; + -o-transition: width 300ms ease-in-out; + transition: width 300ms ease-in-out; +} + +.dropzone .dz-preview.dz-error .dz-error-message { + display: block; +} + +.dropzone .dz-preview.dz-error:hover .dz-error-message { + opacity: 1; + pointer-events: auto; +} + +.dropzone .dz-preview .dz-error-message { + pointer-events: none; + z-index: 1000; + position: absolute; + display: block; + display: none; + opacity: 0; + -webkit-transition: opacity 0.3s ease; + -moz-transition: opacity 0.3s ease; + -ms-transition: opacity 0.3s ease; + -o-transition: opacity 0.3s ease; + transition: opacity 0.3s ease; + border-radius: 8px; + font-size: 13px; + top: 130px; + left: -10px; + width: 140px; + background: #be2626; + background: linear-gradient(to bottom, #be2626, #a92222); + padding: 0.5em 1.2em; + color: white; +} + +.dropzone .dz-preview .dz-error-message:after { + content: ''; + position: absolute; + top: -6px; + left: 64px; + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #be2626; +} diff --git a/web/client/src/environments/environment.prod.ts b/web/client/src/environments/environment.prod.ts new file mode 100644 index 0000000..1b10e01 --- /dev/null +++ b/web/client/src/environments/environment.prod.ts @@ -0,0 +1,13 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +export const environment = { + production: true +}; diff --git a/web/client/src/environments/environment.ts b/web/client/src/environments/environment.ts new file mode 100644 index 0000000..b887b00 --- /dev/null +++ b/web/client/src/environments/environment.ts @@ -0,0 +1,18 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses `environment.ts`, but if you do +// `ng build --env=prod` then `environment.prod.ts` will be used instead. +// The list of which env maps to which file can be found in `.angular-cli.json`. + +export const environment = { + production: false +}; diff --git a/web/client/src/favicon.ico b/web/client/src/favicon.ico new file mode 100644 index 0000000..d456153 Binary files /dev/null and b/web/client/src/favicon.ico differ diff --git a/web/client/src/fonts/MaterialIcons-Regular.ttf b/web/client/src/fonts/MaterialIcons-Regular.ttf new file mode 100644 index 0000000..7015564 Binary files /dev/null and b/web/client/src/fonts/MaterialIcons-Regular.ttf differ diff --git a/web/client/src/fonts/MaterialIcons-Regular.woff b/web/client/src/fonts/MaterialIcons-Regular.woff new file mode 100644 index 0000000..b648a3e Binary files /dev/null and b/web/client/src/fonts/MaterialIcons-Regular.woff differ diff --git a/web/client/src/fonts/MaterialIcons-Regular.woff2 b/web/client/src/fonts/MaterialIcons-Regular.woff2 new file mode 100644 index 0000000..9fa2112 Binary files /dev/null and b/web/client/src/fonts/MaterialIcons-Regular.woff2 differ diff --git a/web/client/src/index.html b/web/client/src/index.html new file mode 100644 index 0000000..6837f8d --- /dev/null +++ b/web/client/src/index.html @@ -0,0 +1,45 @@ + + + + + + Iris Workstation + + + + + + + + + + diff --git a/web/client/src/iris_create_config.json b/web/client/src/iris_create_config.json new file mode 100644 index 0000000..8bbf97d --- /dev/null +++ b/web/client/src/iris_create_config.json @@ -0,0 +1,70 @@ +{ + "server_address": "http://localhost:8080", + "review": false, + "num_of_candidates": 50, + "num_of_candidates_default": 50, + "saveImageData": true, + "showColorPicker": false, + "maxCanvasWidth": 750, + "maxCanvasHeight": 550, + "tools": [ + { + "name": "Crop", + "tool": "CropTool", + "description": "This allows you to crop an area defined by a rectangle.", + "point_radius": 4, + "stroke_width": 2, + "fill_alpha": 0.2, + "type": "IMAGE" + }, + { + "name": "Scale", + "tool": "ScaleTool", + "description": "This allows you to scale an image.", + "type": "IMAGE" + }, + { + "name": "Rotate", + "tool": "RotateTool", + "default_rotation": "10", + "description": "Rotates the image.", + "type": "IMAGE" + }, + { + "name": "Mirror", + "tool": "MirrorTool", + "description": "Flipping or mirroring of an image.", + "type": "IMAGE" + }, + { + "name": "Brightness", + "tool": "BrightnessTool", + "description": "Adjust the brightness of the iris image", + "type": "IMAGE" + }, + { + "name": "Contrast", + "tool": "ContrastTool", + "description": "Adjust the contrast of the iris image", + "type": "IMAGE" + }, + { + "name": "Channel", + "tool": "ChannelTool", + "description": "Extraction of the red color channel from an image.", + "type": "IMAGE" + }, + { + "name": "ColorLevel", + "tool": "ColorLevelTool", + "description": "Modify the color channels from an image.", + "type": "IMAGE" + }, + { + "name": "Sharpen", + "tool": "SharpenTool", + "description": "Sharpen the iris image", + "type": "IMAGE" + } + ] +} diff --git a/web/client/src/iris_review_config.json b/web/client/src/iris_review_config.json new file mode 100644 index 0000000..57b9f27 --- /dev/null +++ b/web/client/src/iris_review_config.json @@ -0,0 +1,144 @@ +{ + "server_address": "http://localhost:8080", + "review": true, + "showColorPicker": true, + "maxCanvasWidth": 750, + "maxCanvasHeight": 550, + "colors": [ + { + "name": "none", + "value": "" + }, + { + "name": "Green", + "value": "#7bd148" + }, + { + "name": "Bold green", + "value": "#51b749" + }, + { + "name": "Blue", + "value": "#a4bdfc" + }, + { + "name": "Bold blue", + "value": "#5484ed" + }, + { + "name": "Turquoise", + "value": "#46d6db" + }, + { + "name": "Yellow", + "value": "#fbd75b" + }, + { + "name": "Orange", + "value": "#ffb878" + }, + { + "name": "Red", + "value": "#ff887c" + }, + { + "name": "Bold red", + "value": "#dc2127" + }, + { + "name": "Purple", + "value": "#dbadff" + } + ], + "tools": [ + { + "name": "Pencil", + "tool": "PencilTool", + "smoothing": true, + "description": "Freehand drawing tool", + "pointRadius": 4, + "strokeWidth": 2, + "fillAlpha": 0.2, + "type": "ANNOTATION" + }, + { + "name": "Circle", + "tool": "CircleFitTool", + "requiredPointCount": 3, + "description": "The radius of the colored part of the eye. Select 3 points on the outer most ring.", + "type": "ANNOTATION" + }, + { + "name": "Point", + "tool": "PointTool", + "description": "The point and shape of point.", + "type": "ANNOTATION" + }, + { + "name": "Polygon", + "tool": "PolygonTool", + "description": "This allows you to mark any areas using a polygon.", + "pointRadius": 4, + "strokeWidth": 2, + "fillAlpha": 0.2, + "type": "ANNOTATION" + }, + { + "name": "Zoom", + "tool": "ZoomTool", + "description": "This allows you to zoom in and out a step at a time.", + "type": "IMAGE" + }, + { + "name": "Rotate", + "tool": "RotateTool", + "default_rotation": "10", + "description": "Rotates the image.", + "type": "IMAGE" + }, + { + "name": "Unroll", + "tool": "UnrollTool", + "requiredPointCount": 3, + "description": "Select 3 points and the system will unroll to a rectangle view", + "type": "IMAGE" + }, + { + "name": "Eyelid", + "tool": "EyelidTool", + "requiredPointCount": 3, + "description": "Mark en eyelid with points.", + "type": "ANNOTATION" + }, + { + "name": "Contrast", + "tool": "ContrastTool", + "description": "Adjust the contrast of the iris image", + "type": "IMAGE" + }, + { + "name": "Brightness", + "tool": "BrightnessTool", + "description": "Adjust the brightness of the iris image", + "type": "IMAGE" + }, + { + "name": "Channel", + "tool": "ChannelTool", + "description": "Extraction of the red color channel from an image.", + "type": "IMAGE" + }, + { + "name": "Sharpen", + "tool": "SharpenTool", + "description": "Sharpen the iris image", + "type": "IMAGE" + }, + { + "name": "AdaptiveContrast", + "tool": "AdaptiveContrastTool", + "description": "Enhance definition of edges within the image", + "type": "IMAGE" + } + ] +} diff --git a/web/client/src/js/vendor/dropzone.js b/web/client/src/js/vendor/dropzone.js new file mode 100644 index 0000000..cc76677 --- /dev/null +++ b/web/client/src/js/vendor/dropzone.js @@ -0,0 +1,3571 @@ +"use strict"; + +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +var _createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; +}(); + +function _possibleConstructorReturn(self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + return call && (typeof call === "object" || typeof call === "function") ? call : self; +} + +function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; +} + +function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +} + +/* + * + * More info at [www.dropzonejs.com](http://www.dropzonejs.com) + * + * Copyright (c) 2012, Matias Meno + * + * 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. + * + */ + +// The Emitter class provides the ability to call `.on()` on Dropzone to listen +// to events. +// It is strongly based on component's emitter class, and I removed the +// functionality because of the dependency hell with different frameworks. +var Emitter = function () { + function Emitter() { + _classCallCheck(this, Emitter); + } + + _createClass(Emitter, [{ + key: "on", + + // Add an event listener for given event + value: function on(event, fn) { + this._callbacks = this._callbacks || {}; + // Create namespace for this event + if (!this._callbacks[event]) { + this._callbacks[event] = []; + } + this._callbacks[event].push(fn); + return this; + } + }, { + key: "emit", + value: function emit(event) { + this._callbacks = this._callbacks || {}; + var callbacks = this._callbacks[event]; + + if (callbacks) { + for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + for (var _iterator = callbacks, _isArray = true, _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator](); ;) { + var _ref; + + if (_isArray) { + if (_i >= _iterator.length) break; + _ref = _iterator[_i++]; + } else { + _i = _iterator.next(); + if (_i.done) break; + _ref = _i.value; + } + + var callback = _ref; + + callback.apply(this, args); + } + } + + return this; + } + + // Remove event listener for given event. If fn is not provided, all event + // listeners for that event will be removed. If neither is provided, all + // event listeners will be removed. + + }, { + key: "off", + value: function off(event, fn) { + if (!this._callbacks || arguments.length === 0) { + this._callbacks = {}; + return this; + } + + // specific event + var callbacks = this._callbacks[event]; + if (!callbacks) { + return this; + } + + // remove all handlers + if (arguments.length === 1) { + delete this._callbacks[event]; + return this; + } + + // remove specific handler + for (var i = 0; i < callbacks.length; i++) { + var callback = callbacks[i]; + if (callback === fn) { + callbacks.splice(i, 1); + break; + } + } + + return this; + } + }]); + + return Emitter; +}(); + +var Dropzone = function (_Emitter) { + _inherits(Dropzone, _Emitter); + + _createClass(Dropzone, null, [{ + key: "initClass", + value: function initClass() { + + // Exposing the emitter class, mainly for tests + this.prototype.Emitter = Emitter; + + /* + This is a list of all available events you can register on a dropzone object. + You can register an event handler like this: + dropzone.on("dragEnter", function() { }); + */ + this.prototype.events = ["drop", "dragstart", "dragend", "dragenter", "dragover", "dragleave", "addedfile", "addedfiles", "removedfile", "thumbnail", "error", "errormultiple", "processing", "processingmultiple", "uploadprogress", "totaluploadprogress", "sending", "sendingmultiple", "success", "successmultiple", "canceled", "canceledmultiple", "complete", "completemultiple", "reset", "maxfilesexceeded", "maxfilesreached", "queuecomplete"]; + + this.prototype.defaultOptions = { + /** + * Has to be specified on elements other than form (or when the form + * doesn't have an `action` attribute). You can also + * provide a function that will be called with `files` and + * must return the url (since `v3.12.0`) + */ + url: null, + + /** + * Can be changed to `"put"` if necessary. You can also provide a function + * that will be called with `files` and must return the method (since `v3.12.0`). + */ + method: "post", + + /** + * Will be set on the XHRequest. + */ + withCredentials: false, + + /** + * The timeout for the XHR requests in milliseconds (since `v4.4.0`). + */ + timeout: 30000, + + /** + * How many file uploads to process in parallel (See the + * Enqueuing file uploads* documentation section for more info) + */ + parallelUploads: 2, + + /** + * Whether to send multiple files in one request. If + * this it set to true, then the fallback file input element will + * have the `multiple` attribute as well. This option will + * also trigger additional events (like `processingmultiple`). See the events + * documentation section for more information. + */ + uploadMultiple: false, + + /** + * Whether you want files to be uploaded in chunks to your server. This can't be + * used in combination with `uploadMultiple`. + * + * See [chunksUploaded](#config-chunksUploaded) for the callback to finalise an upload. + */ + chunking: false, + + /** + * If `chunking` is enabled, this defines whether **every** file should be chunked, + * even if the file size is below chunkSize. This means, that the additional chunk + * form data will be submitted and the `chunksUploaded` callback will be invoked. + */ + forceChunking: false, + + /** + * If `chunking` is `true`, then this defines the chunk size in bytes. + */ + chunkSize: 2000000, + + /** + * If `true`, the individual chunks of a file are being uploaded simultaneously. + */ + parallelChunkUploads: false, + + /** + * Whether a chunk should be retried if it fails. + */ + retryChunks: false, + + /** + * If `retryChunks` is true, how many times should it be retried. + */ + retryChunksLimit: 3, + + /** + * If not `null` defines how many files this Dropzone handles. If it exceeds, + * the event `maxfilesexceeded` will be called. The dropzone element gets the + * class `dz-max-files-reached` accordingly so you can provide visual feedback. + */ + maxFilesize: 256, + + /** + * The name of the file param that gets transferred. + * **NOTE**: If you have the option `uploadMultiple` set to `true`, then + * Dropzone will append `[]` to the name. + */ + paramName: "file", + + /** + * Whether thumbnails for images should be generated + */ + createImageThumbnails: true, + + /** + * In MB. When the filename exceeds this limit, the thumbnail will not be generated. + */ + maxThumbnailFilesize: 10, + + /** + * If `null`, the ratio of the image will be used to calculate it. + */ + thumbnailWidth: 120, + + /** + * The same as `thumbnailWidth`. If both are null, images will not be resized. + */ + thumbnailHeight: 120, + + /** + * How the images should be scaled down in case both, `thumbnailWidth` and `thumbnailHeight` are provided. + * Can be either `contain` or `crop`. + */ + thumbnailMethod: 'crop', + + /** + * If set, images will be resized to these dimensions before being **uploaded**. + * If only one, `resizeWidth` **or** `resizeHeight` is provided, the original aspect + * ratio of the file will be preserved. + * + * The `options.transformFile` function uses these options, so if the `transformFile` function + * is overridden, these options don't do anything. + */ + resizeWidth: null, + + /** + * See `resizeWidth`. + */ + resizeHeight: null, + + /** + * The mime type of the resized image (before it gets uploaded to the server). + * If `null` the original mime type will be used. To force jpeg, for example, use `image/jpeg`. + * See `resizeWidth` for more information. + */ + resizeMimeType: null, + + /** + * The quality of the resized images. See `resizeWidth`. + */ + resizeQuality: 0.8, + + /** + * How the images should be scaled down in case both, `resizeWidth` and `resizeHeight` are provided. + * Can be either `contain` or `crop`. + */ + resizeMethod: 'contain', + + /** + * The base that is used to calculate the filesize. You can change this to + * 1024 if you would rather display kibibytes, mebibytes, etc... + * 1024 is technically incorrect, because `1024 bytes` are `1 kibibyte` not `1 kilobyte`. + * You can change this to `1024` if you don't care about validity. + */ + filesizeBase: 1000, + + /** + * Can be used to limit the maximum number of files that will be handled by this Dropzone + */ + maxFiles: null, + + /** + * An optional object to send additional headers to the server. Eg: + * `{ "My-Awesome-Header": "header value" }` + */ + headers: null, + + /** + * If `true`, the dropzone element itself will be clickable, if `false` + * nothing will be clickable. + * + * You can also pass an HTML element, a CSS selector (for multiple elements) + * or an array of those. In that case, all of those elements will trigger an + * upload when clicked. + */ + clickable: true, + + /** + * Whether hidden files in directories should be ignored. + */ + ignoreHiddenFiles: true, + + /** + * The default implementation of `accept` checks the file's mime type or + * extension against this list. This is a comma separated list of mime + * types or file extensions. + * + * Eg.: `image/*,application/pdf,.psd` + * + * If the Dropzone is `clickable` this option will also be used as + * [`accept`](https://developer.mozilla.org/en-US/docs/HTML/Element/input#attr-accept) + * parameter on the hidden file input as well. + */ + acceptedFiles: null, + + /** + * **Deprecated!** + * Use acceptedFiles instead. + */ + acceptedMimeTypes: null, + + /** + * If false, files will be added to the queue but the queue will not be + * processed automatically. + * This can be useful if you need some additional user input before sending + * files (or if you want want all files sent at once). + * If you're ready to send the file simply call `myDropzone.processQueue()`. + * + * See the [enqueuing file uploads](#enqueuing-file-uploads) documentation + * section for more information. + */ + autoProcessQueue: true, + + /** + * If false, files added to the dropzone will not be queued by default. + * You'll have to call `enqueueFile(file)` manually. + */ + autoQueue: true, + + /** + * If `true`, this will add a link to every file preview to remove or cancel (if + * already uploading) the file. The `dictCancelUpload`, `dictCancelUploadConfirmation` + * and `dictRemoveFile` options are used for the wording. + */ + addRemoveLinks: false, + + /** + * Defines where to display the file previews – if `null` the + * Dropzone element itself is used. Can be a plain `HTMLElement` or a CSS + * selector. The element should have the `dropzone-previews` class so + * the previews are displayed properly. + */ + previewsContainer: null, + + /** + * This is the element the hidden input field (which is used when clicking on the + * dropzone to trigger file selection) will be appended to. This might + * be important in case you use frameworks to switch the content of your page. + */ + hiddenInputContainer: "body", + + /** + * If null, no capture type will be specified + * If camera, mobile devices will skip the file selection and choose camera + * If microphone, mobile devices will skip the file selection and choose the microphone + * If camcorder, mobile devices will skip the file selection and choose the camera in video mode + * On apple devices multiple must be set to false. AcceptedFiles may need to + * be set to an appropriate mime type (e.g. "image/*", "audio/*", or "video/*"). + */ + capture: null, + + /** + * **Deprecated**. Use `renameFile` instead. + */ + renameFilename: null, + + /** + * A function that is invoked before the file is uploaded to the server and renames the file. + * This function gets the `File` as argument and can use the `file.name`. The actual name of the + * file that gets used during the upload can be accessed through `file.upload.filename`. + */ + renameFile: null, + + /** + * If `true` the fallback will be forced. This is very useful to test your server + * implementations first and make sure that everything works as + * expected without dropzone if you experience problems, and to test + * how your fallbacks will look. + */ + forceFallback: false, + + /** + * The text used before any files are dropped. + */ + dictDefaultMessage: "Drop files here to upload", + + /** + * The text that replaces the default message text it the browser is not supported. + */ + dictFallbackMessage: "Your browser does not support drag'n'drop file uploads.", + + /** + * The text that will be added before the fallback form. + * If you provide a fallback element yourself, or if this option is `null` this will + * be ignored. + */ + dictFallbackText: "Please use the fallback form below to upload your files like in the olden days.", + + /** + * If the filesize is too big. + * `{{filesize}}` and `{{maxFilesize}}` will be replaced with the respective configuration values. + */ + dictFileTooBig: "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.", + + /** + * If the file doesn't match the file type. + */ + dictInvalidFileType: "You can't upload files of this type.", + + /** + * If the server response was invalid. + * `{{statusCode}}` will be replaced with the servers status code. + */ + dictResponseError: "Server responded with {{statusCode}} code.", + + /** + * If `addRemoveLinks` is true, the text to be used for the cancel upload link. + */ + dictCancelUpload: "Cancel upload", + + /** + * If `addRemoveLinks` is true, the text to be used for confirmation when cancelling upload. + */ + dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?", + + /** + * If `addRemoveLinks` is true, the text to be used to remove a file. + */ + dictRemoveFile: "Remove file", + + /** + * If this is not null, then the user will be prompted before removing a file. + */ + dictRemoveFileConfirmation: null, + + /** + * Displayed if `maxFiles` is st and exceeded. + * The string `{{maxFiles}}` will be replaced by the configuration value. + */ + dictMaxFilesExceeded: "You can not upload any more files.", + + /** + * Allows you to translate the different units. Starting with `tb` for terabytes and going down to + * `b` for bytes. + */ + dictFileSizeUnits: {tb: "TB", gb: "GB", mb: "MB", kb: "KB", b: "b"}, + + /** + * Called when dropzone initialized + * You can add event listeners here + */ + init: function init() { + }, + + + /** + * Can be an **object** of additional parameters to transfer to the server, **or** a `Function` + * that gets invoked with the `files`, `xhr` and, if it's a chunked upload, `chunk` arguments. In case + * of a function, this needs to return a map. + * + * The default implementation does nothing for normal uploads, but adds relevant information for + * chunked uploads. + * + * This is the same as adding hidden input fields in the form element. + */ + params: function params(files, xhr, chunk) { + if (chunk) { + return { + dzuuid: chunk.file.upload.uuid, + dzchunkindex: chunk.index, + dztotalfilesize: chunk.file.size, + dzchunksize: this.options.chunkSize, + dztotalchunkcount: chunk.file.upload.totalChunkCount, + dzchunkbyteoffset: chunk.index * this.options.chunkSize + }; + } + }, + + + /** + * A function that gets a [file](https://developer.mozilla.org/en-US/docs/DOM/File) + * and a `done` function as parameters. + * + * If the done function is invoked without arguments, the file is "accepted" and will + * be processed. If you pass an error message, the file is rejected, and the error + * message will be displayed. + * This function will not be called if the file is too big or doesn't match the mime types. + */ + accept: function accept(file, done) { + return done(); + }, + + + /** + * The callback that will be invoked when all chunks have been uploaded for a file. + * It gets the file for which the chunks have been uploaded as the first parameter, + * and the `done` function as second. `done()` needs to be invoked when everything + * needed to finish the upload process is done. + */ + chunksUploaded: function chunksUploaded(file, done) { + done(); + }, + + /** + * Gets called when the browser is not supported. + * The default implementation shows the fallback input field and adds + * a text. + */ + fallback: function fallback() { + // This code should pass in IE7... :( + var messageElement = void 0; + this.element.className = this.element.className + " dz-browser-not-supported"; + + for (var _iterator2 = this.element.getElementsByTagName("div"), _isArray2 = true, _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator](); ;) { + var _ref2; + + if (_isArray2) { + if (_i2 >= _iterator2.length) break; + _ref2 = _iterator2[_i2++]; + } else { + _i2 = _iterator2.next(); + if (_i2.done) break; + _ref2 = _i2.value; + } + + var child = _ref2; + + if (/(^| )dz-message($| )/.test(child.className)) { + messageElement = child; + child.className = "dz-message"; // Removes the 'dz-default' class + break; + } + } + if (!messageElement) { + messageElement = Dropzone.createElement("
"); + this.element.appendChild(messageElement); + } + + var span = messageElement.getElementsByTagName("span")[0]; + if (span) { + if (span.textContent != null) { + span.textContent = this.options.dictFallbackMessage; + } else if (span.innerText != null) { + span.innerText = this.options.dictFallbackMessage; + } + } + + return this.element.appendChild(this.getFallbackForm()); + }, + + + /** + * Gets called to calculate the thumbnail dimensions. + * + * It gets `file`, `width` and `height` (both may be `null`) as parameters and must return an object containing: + * + * - `srcWidth` & `srcHeight` (required) + * - `trgWidth` & `trgHeight` (required) + * - `srcX` & `srcY` (optional, default `0`) + * - `trgX` & `trgY` (optional, default `0`) + * + * Those values are going to be used by `ctx.drawImage()`. + */ + resize: function resize(file, width, height, resizeMethod) { + var info = { + srcX: 0, + srcY: 0, + srcWidth: file.width, + srcHeight: file.height + }; + + var srcRatio = file.width / file.height; + + // Automatically calculate dimensions if not specified + if (width == null && height == null) { + width = info.srcWidth; + height = info.srcHeight; + } else if (width == null) { + width = height * srcRatio; + } else if (height == null) { + height = width / srcRatio; + } + + // Make sure images aren't upscaled + width = Math.min(width, info.srcWidth); + height = Math.min(height, info.srcHeight); + + var trgRatio = width / height; + + if (info.srcWidth > width || info.srcHeight > height) { + // Image is bigger and needs rescaling + if (resizeMethod === 'crop') { + if (srcRatio > trgRatio) { + info.srcHeight = file.height; + info.srcWidth = info.srcHeight * trgRatio; + } else { + info.srcWidth = file.width; + info.srcHeight = info.srcWidth / trgRatio; + } + } else if (resizeMethod === 'contain') { + // Method 'contain' + if (srcRatio > trgRatio) { + height = width / srcRatio; + } else { + width = height * srcRatio; + } + } else { + throw new Error("Unknown resizeMethod '" + resizeMethod + "'"); + } + } + + info.srcX = (file.width - info.srcWidth) / 2; + info.srcY = (file.height - info.srcHeight) / 2; + + info.trgWidth = width; + info.trgHeight = height; + + return info; + }, + + + /** + * Can be used to transform the file (for example, resize an image if necessary). + * + * The default implementation uses `resizeWidth` and `resizeHeight` (if provided) and resizes + * images according to those dimensions. + * + * Gets the `file` as the first parameter, and a `done()` function as the second, that needs + * to be invoked with the file when the transformation is done. + */ + transformFile: function transformFile(file, done) { + if ((this.options.resizeWidth || this.options.resizeHeight) && file.type.match(/image.*/)) { + return this.resizeImage(file, this.options.resizeWidth, this.options.resizeHeight, this.options.resizeMethod, done); + } else { + return done(file); + } + }, + + + /** + * A string that contains the template used for each dropped + * file. Change it to fulfill your needs but make sure to properly + * provide all elements. + * + * If you want to use an actual HTML element instead of providing a String + * as a config option, you could create a div with the id `tpl`, + * put the template inside it and provide the element like this: + * + * document + * .querySelector('#tpl') + * .innerHTML + * + */ + previewTemplate: "
\n
\n
\n
\n
\n
\n
\n
\n
\n \n Check\n \n \n \n \n \n
\n
\n \n Error\n \n \n \n \n \n \n \n
\n
", + + // END OPTIONS + // (Required by the dropzone documentation parser) + + + /* + Those functions register themselves to the events on init and handle all + the user interface specific stuff. Overwriting them won't break the upload + but can break the way it's displayed. + You can overwrite them if you don't like the default behavior. If you just + want to add an additional event handler, register it on the dropzone object + and don't overwrite those options. + */ + + // Those are self explanatory and simply concern the DragnDrop. + drop: function drop(e) { + return this.element.classList.remove("dz-drag-hover"); + }, + dragstart: function dragstart(e) { + }, + dragend: function dragend(e) { + return this.element.classList.remove("dz-drag-hover"); + }, + dragenter: function dragenter(e) { + return this.element.classList.add("dz-drag-hover"); + }, + dragover: function dragover(e) { + return this.element.classList.add("dz-drag-hover"); + }, + dragleave: function dragleave(e) { + return this.element.classList.remove("dz-drag-hover"); + }, + paste: function paste(e) { + }, + + + // Called whenever there are no files left in the dropzone anymore, and the + // dropzone should be displayed as if in the initial state. + reset: function reset() { + return this.element.classList.remove("dz-started"); + }, + + + // Called when a file is added to the queue + // Receives `file` + addedfile: function addedfile(file) { + var _this2 = this; + + if (this.element === this.previewsContainer) { + this.element.classList.add("dz-started"); + } + + if (this.previewsContainer) { + file.previewElement = Dropzone.createElement(this.options.previewTemplate.trim()); + file.previewTemplate = file.previewElement; // Backwards compatibility + + this.previewsContainer.appendChild(file.previewElement); + for (var _iterator3 = file.previewElement.querySelectorAll("[data-dz-name]"), _isArray3 = true, _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator](); ;) { + var _ref3; + + if (_isArray3) { + if (_i3 >= _iterator3.length) break; + _ref3 = _iterator3[_i3++]; + } else { + _i3 = _iterator3.next(); + if (_i3.done) break; + _ref3 = _i3.value; + } + + var node = _ref3; + + node.textContent = file.name; + } + for (var _iterator4 = file.previewElement.querySelectorAll("[data-dz-size]"), _isArray4 = true, _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator](); ;) { + if (_isArray4) { + if (_i4 >= _iterator4.length) break; + node = _iterator4[_i4++]; + } else { + _i4 = _iterator4.next(); + if (_i4.done) break; + node = _i4.value; + } + + node.innerHTML = this.filesize(file.size); + } + + if (this.options.addRemoveLinks) { + file._removeLink = Dropzone.createElement("" + this.options.dictRemoveFile + ""); + file.previewElement.appendChild(file._removeLink); + } + + var removeFileEvent = function removeFileEvent(e) { + e.preventDefault(); + e.stopPropagation(); + if (file.status === Dropzone.UPLOADING) { + return Dropzone.confirm(_this2.options.dictCancelUploadConfirmation, function () { + return _this2.removeFile(file); + }); + } else { + if (_this2.options.dictRemoveFileConfirmation) { + return Dropzone.confirm(_this2.options.dictRemoveFileConfirmation, function () { + return _this2.removeFile(file); + }); + } else { + return _this2.removeFile(file); + } + } + }; + + for (var _iterator5 = file.previewElement.querySelectorAll("[data-dz-remove]"), _isArray5 = true, _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator](); ;) { + var _ref4; + + if (_isArray5) { + if (_i5 >= _iterator5.length) break; + _ref4 = _iterator5[_i5++]; + } else { + _i5 = _iterator5.next(); + if (_i5.done) break; + _ref4 = _i5.value; + } + + var removeLink = _ref4; + + removeLink.addEventListener("click", removeFileEvent); + } + } + }, + + + // Called whenever a file is removed. + removedfile: function removedfile(file) { + if (file.previewElement != null && file.previewElement.parentNode != null) { + file.previewElement.parentNode.removeChild(file.previewElement); + } + return this._updateMaxFilesReachedClass(); + }, + + + // Called when a thumbnail has been generated + // Receives `file` and `dataUrl` + thumbnail: function thumbnail(file, dataUrl) { + if (file.previewElement) { + file.previewElement.classList.remove("dz-file-preview"); + for (var _iterator6 = file.previewElement.querySelectorAll("[data-dz-thumbnail]"), _isArray6 = true, _i6 = 0, _iterator6 = _isArray6 ? _iterator6 : _iterator6[Symbol.iterator](); ;) { + var _ref5; + + if (_isArray6) { + if (_i6 >= _iterator6.length) break; + _ref5 = _iterator6[_i6++]; + } else { + _i6 = _iterator6.next(); + if (_i6.done) break; + _ref5 = _i6.value; + } + + var thumbnailElement = _ref5; + + thumbnailElement.alt = file.name; + thumbnailElement.src = dataUrl; + } + + return setTimeout(function () { + return file.previewElement.classList.add("dz-image-preview"); + }, 1); + } + }, + + + // Called whenever an error occurs + // Receives `file` and `message` + error: function error(file, message) { + if (file.previewElement) { + file.previewElement.classList.add("dz-error"); + if (typeof message !== "String" && message.error) { + message = message.error; + } + for (var _iterator7 = file.previewElement.querySelectorAll("[data-dz-errormessage]"), _isArray7 = true, _i7 = 0, _iterator7 = _isArray7 ? _iterator7 : _iterator7[Symbol.iterator](); ;) { + var _ref6; + + if (_isArray7) { + if (_i7 >= _iterator7.length) break; + _ref6 = _iterator7[_i7++]; + } else { + _i7 = _iterator7.next(); + if (_i7.done) break; + _ref6 = _i7.value; + } + + var node = _ref6; + + node.textContent = message; + } + } + }, + errormultiple: function errormultiple() { + }, + + + // Called when a file gets processed. Since there is a cue, not all added + // files are processed immediately. + // Receives `file` + processing: function processing(file) { + if (file.previewElement) { + file.previewElement.classList.add("dz-processing"); + if (file._removeLink) { + return file._removeLink.textContent = this.options.dictCancelUpload; + } + } + }, + processingmultiple: function processingmultiple() { + }, + + + // Called whenever the upload progress gets updated. + // Receives `file`, `progress` (percentage 0-100) and `bytesSent`. + // To get the total number of bytes of the file, use `file.size` + uploadprogress: function uploadprogress(file, progress, bytesSent) { + if (file.previewElement) { + for (var _iterator8 = file.previewElement.querySelectorAll("[data-dz-uploadprogress]"), _isArray8 = true, _i8 = 0, _iterator8 = _isArray8 ? _iterator8 : _iterator8[Symbol.iterator](); ;) { + var _ref7; + + if (_isArray8) { + if (_i8 >= _iterator8.length) break; + _ref7 = _iterator8[_i8++]; + } else { + _i8 = _iterator8.next(); + if (_i8.done) break; + _ref7 = _i8.value; + } + + var node = _ref7; + + node.nodeName === 'PROGRESS' ? node.value = progress : node.style.width = progress + "%"; + } + } + }, + + + // Called whenever the total upload progress gets updated. + // Called with totalUploadProgress (0-100), totalBytes and totalBytesSent + totaluploadprogress: function totaluploadprogress() { + }, + + + // Called just before the file is sent. Gets the `xhr` object as second + // parameter, so you can modify it (for example to add a CSRF token) and a + // `formData` object to add additional information. + sending: function sending() { + }, + sendingmultiple: function sendingmultiple() { + }, + + + // When the complete upload is finished and successful + // Receives `file` + success: function success(file) { + if (file.previewElement) { + return file.previewElement.classList.add("dz-success"); + } + }, + successmultiple: function successmultiple() { + }, + + + // When the upload is canceled. + canceled: function canceled(file) { + return this.emit("error", file, "Upload canceled."); + }, + canceledmultiple: function canceledmultiple() { + }, + + + // When the upload is finished, either with success or an error. + // Receives `file` + complete: function complete(file) { + if (file._removeLink) { + file._removeLink.textContent = this.options.dictRemoveFile; + } + if (file.previewElement) { + return file.previewElement.classList.add("dz-complete"); + } + }, + completemultiple: function completemultiple() { + }, + maxfilesexceeded: function maxfilesexceeded() { + }, + maxfilesreached: function maxfilesreached() { + }, + queuecomplete: function queuecomplete() { + }, + addedfiles: function addedfiles() { + } + }; + + this.prototype._thumbnailQueue = []; + this.prototype._processingThumbnail = false; + } + + // global utility + + }, { + key: "extend", + value: function extend(target) { + for (var _len2 = arguments.length, objects = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { + objects[_key2 - 1] = arguments[_key2]; + } + + for (var _iterator9 = objects, _isArray9 = true, _i9 = 0, _iterator9 = _isArray9 ? _iterator9 : _iterator9[Symbol.iterator](); ;) { + var _ref8; + + if (_isArray9) { + if (_i9 >= _iterator9.length) break; + _ref8 = _iterator9[_i9++]; + } else { + _i9 = _iterator9.next(); + if (_i9.done) break; + _ref8 = _i9.value; + } + + var object = _ref8; + + for (var key in object) { + var val = object[key]; + target[key] = val; + } + } + return target; + } + }]); + + function Dropzone(el, options) { + _classCallCheck(this, Dropzone); + + var _this = _possibleConstructorReturn(this, (Dropzone.__proto__ || Object.getPrototypeOf(Dropzone)).call(this)); + + var fallback = void 0, + left = void 0; + _this.element = el; + // For backwards compatibility since the version was in the prototype previously + _this.version = Dropzone.version; + + _this.defaultOptions.previewTemplate = _this.defaultOptions.previewTemplate.replace(/\n*/g, ""); + + _this.clickableElements = []; + _this.listeners = []; + _this.files = []; // All files + + if (typeof _this.element === "string") { + _this.element = document.querySelector(_this.element); + } + + // Not checking if instance of HTMLElement or Element since IE9 is extremely weird. + if (!_this.element || _this.element.nodeType == null) { + throw new Error("Invalid dropzone element."); + } + + if (_this.element.dropzone) { + throw new Error("Dropzone already attached."); + } + + // Now add this dropzone to the instances. + Dropzone.instances.push(_this); + + // Put the dropzone inside the element itself. + _this.element.dropzone = _this; + + var elementOptions = (left = Dropzone.optionsForElement(_this.element)) != null ? left : {}; + + _this.options = Dropzone.extend({}, _this.defaultOptions, elementOptions, options != null ? options : {}); + + // If the browser failed, just call the fallback and leave + if (_this.options.forceFallback || !Dropzone.isBrowserSupported()) { + var _ret; + + return _ret = _this.options.fallback.call(_this), _possibleConstructorReturn(_this, _ret); + } + + // @options.url = @element.getAttribute "action" unless @options.url? + if (_this.options.url == null) { + _this.options.url = _this.element.getAttribute("action"); + } + + if (!_this.options.url) { + throw new Error("No URL provided."); + } + + if (_this.options.acceptedFiles && _this.options.acceptedMimeTypes) { + throw new Error("You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated."); + } + + if (_this.options.uploadMultiple && _this.options.chunking) { + throw new Error('You cannot set both: uploadMultiple and chunking.'); + } + + // Backwards compatibility + if (_this.options.acceptedMimeTypes) { + _this.options.acceptedFiles = _this.options.acceptedMimeTypes; + delete _this.options.acceptedMimeTypes; + } + + // Backwards compatibility + if (_this.options.renameFilename != null) { + _this.options.renameFile = function (file) { + return _this.options.renameFilename.call(_this, file.name, file); + }; + } + + _this.options.method = _this.options.method.toUpperCase(); + + if ((fallback = _this.getExistingFallback()) && fallback.parentNode) { + // Remove the fallback + fallback.parentNode.removeChild(fallback); + } + + // Display previews in the previewsContainer element or the Dropzone element unless explicitly set to false + if (_this.options.previewsContainer !== false) { + if (_this.options.previewsContainer) { + _this.previewsContainer = Dropzone.getElement(_this.options.previewsContainer, "previewsContainer"); + } else { + _this.previewsContainer = _this.element; + } + } + + if (_this.options.clickable) { + if (_this.options.clickable === true) { + _this.clickableElements = [_this.element]; + } else { + _this.clickableElements = Dropzone.getElements(_this.options.clickable, "clickable"); + } + } + + _this.init(); + return _this; + } + + // Returns all files that have been accepted + + + _createClass(Dropzone, [{ + key: "getAcceptedFiles", + value: function getAcceptedFiles() { + return this.files.filter(function (file) { + return file.accepted; + }).map(function (file) { + return file; + }); + } + + // Returns all files that have been rejected + // Not sure when that's going to be useful, but added for completeness. + + }, { + key: "getRejectedFiles", + value: function getRejectedFiles() { + return this.files.filter(function (file) { + return !file.accepted; + }).map(function (file) { + return file; + }); + } + }, { + key: "getFilesWithStatus", + value: function getFilesWithStatus(status) { + return this.files.filter(function (file) { + return file.status === status; + }).map(function (file) { + return file; + }); + } + + // Returns all files that are in the queue + + }, { + key: "getQueuedFiles", + value: function getQueuedFiles() { + return this.getFilesWithStatus(Dropzone.QUEUED); + } + }, { + key: "getUploadingFiles", + value: function getUploadingFiles() { + return this.getFilesWithStatus(Dropzone.UPLOADING); + } + }, { + key: "getAddedFiles", + value: function getAddedFiles() { + return this.getFilesWithStatus(Dropzone.ADDED); + } + + // Files that are either queued or uploading + + }, { + key: "getActiveFiles", + value: function getActiveFiles() { + return this.files.filter(function (file) { + return file.status === Dropzone.UPLOADING || file.status === Dropzone.QUEUED; + }).map(function (file) { + return file; + }); + } + + // The function that gets called when Dropzone is initialized. You + // can (and should) setup event listeners inside this function. + + }, { + key: "init", + value: function init() { + var _this3 = this; + + // In case it isn't set already + if (this.element.tagName === "form") { + this.element.setAttribute("enctype", "multipart/form-data"); + } + + if (this.element.classList.contains("dropzone") && !this.element.querySelector(".dz-message")) { + this.element.appendChild(Dropzone.createElement("
" + this.options.dictDefaultMessage + "
")); + } + + if (this.clickableElements.length) { + var setupHiddenFileInput = function setupHiddenFileInput() { + if (_this3.hiddenFileInput) { + _this3.hiddenFileInput.parentNode.removeChild(_this3.hiddenFileInput); + } + _this3.hiddenFileInput = document.createElement("input"); + _this3.hiddenFileInput.setAttribute("type", "file"); + if (_this3.options.maxFiles === null || _this3.options.maxFiles > 1) { + _this3.hiddenFileInput.setAttribute("multiple", "multiple"); + } + _this3.hiddenFileInput.className = "dz-hidden-input"; + + if (_this3.options.acceptedFiles !== null) { + _this3.hiddenFileInput.setAttribute("accept", _this3.options.acceptedFiles); + } + if (_this3.options.capture !== null) { + _this3.hiddenFileInput.setAttribute("capture", _this3.options.capture); + } + + // Not setting `display="none"` because some browsers don't accept clicks + // on elements that aren't displayed. + _this3.hiddenFileInput.style.visibility = "hidden"; + _this3.hiddenFileInput.style.position = "absolute"; + _this3.hiddenFileInput.style.top = "0"; + _this3.hiddenFileInput.style.left = "0"; + _this3.hiddenFileInput.style.height = "0"; + _this3.hiddenFileInput.style.width = "0"; + document.querySelector(_this3.options.hiddenInputContainer).appendChild(_this3.hiddenFileInput); + return _this3.hiddenFileInput.addEventListener("change", function () { + var files = _this3.hiddenFileInput.files; + + if (files.length) { + for (var _iterator10 = files, _isArray10 = true, _i10 = 0, _iterator10 = _isArray10 ? _iterator10 : _iterator10[Symbol.iterator](); ;) { + var _ref9; + + if (_isArray10) { + if (_i10 >= _iterator10.length) break; + _ref9 = _iterator10[_i10++]; + } else { + _i10 = _iterator10.next(); + if (_i10.done) break; + _ref9 = _i10.value; + } + + var file = _ref9; + + _this3.addFile(file); + } + } + _this3.emit("addedfiles", files); + return setupHiddenFileInput(); + }); + }; + setupHiddenFileInput(); + } + + this.URL = window.URL !== null ? window.URL : window.webkitURL; + + // Setup all event listeners on the Dropzone object itself. + // They're not in @setupEventListeners() because they shouldn't be removed + // again when the dropzone gets disabled. + for (var _iterator11 = this.events, _isArray11 = true, _i11 = 0, _iterator11 = _isArray11 ? _iterator11 : _iterator11[Symbol.iterator](); ;) { + var _ref10; + + if (_isArray11) { + if (_i11 >= _iterator11.length) break; + _ref10 = _iterator11[_i11++]; + } else { + _i11 = _iterator11.next(); + if (_i11.done) break; + _ref10 = _i11.value; + } + + var eventName = _ref10; + + this.on(eventName, this.options[eventName]); + } + + this.on("uploadprogress", function () { + return _this3.updateTotalUploadProgress(); + }); + + this.on("removedfile", function () { + return _this3.updateTotalUploadProgress(); + }); + + this.on("canceled", function (file) { + return _this3.emit("complete", file); + }); + + // Emit a `queuecomplete` event if all files finished uploading. + this.on("complete", function (file) { + if (_this3.getAddedFiles().length === 0 && _this3.getUploadingFiles().length === 0 && _this3.getQueuedFiles().length === 0) { + // This needs to be deferred so that `queuecomplete` really triggers after `complete` + return setTimeout(function () { + return _this3.emit("queuecomplete"); + }, 0); + } + }); + + var noPropagation = function noPropagation(e) { + e.stopPropagation(); + if (e.preventDefault) { + return e.preventDefault(); + } else { + return e.returnValue = false; + } + }; + + // Create the listeners + this.listeners = [{ + element: this.element, + events: { + "dragstart": function dragstart(e) { + return _this3.emit("dragstart", e); + }, + "dragenter": function dragenter(e) { + noPropagation(e); + return _this3.emit("dragenter", e); + }, + "dragover": function dragover(e) { + // Makes it possible to drag files from chrome's download bar + // http://stackoverflow.com/questions/19526430/drag-and-drop-file-uploads-from-chrome-downloads-bar + // Try is required to prevent bug in Internet Explorer 11 (SCRIPT65535 exception) + var efct = void 0; + try { + efct = e.dataTransfer.effectAllowed; + } catch (error) { + } + e.dataTransfer.dropEffect = 'move' === efct || 'linkMove' === efct ? 'move' : 'copy'; + + noPropagation(e); + return _this3.emit("dragover", e); + }, + "dragleave": function dragleave(e) { + return _this3.emit("dragleave", e); + }, + "drop": function drop(e) { + noPropagation(e); + return _this3.drop(e); + }, + "dragend": function dragend(e) { + return _this3.emit("dragend", e); + } + + // This is disabled right now, because the browsers don't implement it properly. + // "paste": (e) => + // noPropagation e + // @paste e + } + }]; + + this.clickableElements.forEach(function (clickableElement) { + return _this3.listeners.push({ + element: clickableElement, + events: { + "click": function click(evt) { + // Only the actual dropzone or the message element should trigger file selection + if (clickableElement !== _this3.element || evt.target === _this3.element || Dropzone.elementInside(evt.target, _this3.element.querySelector(".dz-message"))) { + _this3.hiddenFileInput.click(); // Forward the click + } + return true; + } + } + }); + }); + + this.enable(); + + return this.options.init.call(this); + } + + // Not fully tested yet + + }, { + key: "destroy", + value: function destroy() { + this.disable(); + this.removeAllFiles(true); + if (this.hiddenFileInput != null ? this.hiddenFileInput.parentNode : undefined) { + this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput); + this.hiddenFileInput = null; + } + delete this.element.dropzone; + return Dropzone.instances.splice(Dropzone.instances.indexOf(this), 1); + } + }, { + key: "updateTotalUploadProgress", + value: function updateTotalUploadProgress() { + var totalUploadProgress = void 0; + var totalBytesSent = 0; + var totalBytes = 0; + + var activeFiles = this.getActiveFiles(); + + if (activeFiles.length) { + for (var _iterator12 = this.getActiveFiles(), _isArray12 = true, _i12 = 0, _iterator12 = _isArray12 ? _iterator12 : _iterator12[Symbol.iterator](); ;) { + var _ref11; + + if (_isArray12) { + if (_i12 >= _iterator12.length) break; + _ref11 = _iterator12[_i12++]; + } else { + _i12 = _iterator12.next(); + if (_i12.done) break; + _ref11 = _i12.value; + } + + var file = _ref11; + + totalBytesSent += file.upload.bytesSent; + totalBytes += file.upload.total; + } + totalUploadProgress = 100 * totalBytesSent / totalBytes; + } else { + totalUploadProgress = 100; + } + + return this.emit("totaluploadprogress", totalUploadProgress, totalBytes, totalBytesSent); + } + + // @options.paramName can be a function taking one parameter rather than a string. + // A parameter name for a file is obtained simply by calling this with an index number. + + }, { + key: "_getParamName", + value: function _getParamName(n) { + if (typeof this.options.paramName === "function") { + return this.options.paramName(n); + } else { + return "" + this.options.paramName + (this.options.uploadMultiple ? "[" + n + "]" : ""); + } + } + + // If @options.renameFile is a function, + // the function will be used to rename the file.name before appending it to the formData + + }, { + key: "_renameFile", + value: function _renameFile(file) { + if (typeof this.options.renameFile !== "function") { + return file.name; + } + return this.options.renameFile(file); + } + + // Returns a form that can be used as fallback if the browser does not support DragnDrop + // + // If the dropzone is already a form, only the input field and button are returned. Otherwise a complete form element is provided. + // This code has to pass in IE7 :( + + }, { + key: "getFallbackForm", + value: function getFallbackForm() { + var existingFallback = void 0, + form = void 0; + if (existingFallback = this.getExistingFallback()) { + return existingFallback; + } + + var fieldsString = "
"; + if (this.options.dictFallbackText) { + fieldsString += "

" + this.options.dictFallbackText + "

"; + } + fieldsString += "
"; + + var fields = Dropzone.createElement(fieldsString); + if (this.element.tagName !== "FORM") { + form = Dropzone.createElement("
"); + form.appendChild(fields); + } else { + // Make sure that the enctype and method attributes are set properly + this.element.setAttribute("enctype", "multipart/form-data"); + this.element.setAttribute("method", this.options.method); + } + return form != null ? form : fields; + } + + // Returns the fallback elements if they exist already + // + // This code has to pass in IE7 :( + + }, { + key: "getExistingFallback", + value: function getExistingFallback() { + var getFallback = function getFallback(elements) { + for (var _iterator13 = elements, _isArray13 = true, _i13 = 0, _iterator13 = _isArray13 ? _iterator13 : _iterator13[Symbol.iterator](); ;) { + var _ref12; + + if (_isArray13) { + if (_i13 >= _iterator13.length) break; + _ref12 = _iterator13[_i13++]; + } else { + _i13 = _iterator13.next(); + if (_i13.done) break; + _ref12 = _i13.value; + } + + var el = _ref12; + + if (/(^| )fallback($| )/.test(el.className)) { + return el; + } + } + }; + + var _arr = ["div", "form"]; + for (var _i14 = 0; _i14 < _arr.length; _i14++) { + var tagName = _arr[_i14]; + var fallback; + if (fallback = getFallback(this.element.getElementsByTagName(tagName))) { + return fallback; + } + } + } + + // Activates all listeners stored in @listeners + + }, { + key: "setupEventListeners", + value: function setupEventListeners() { + return this.listeners.map(function (elementListeners) { + return function () { + var result = []; + for (var event in elementListeners.events) { + var listener = elementListeners.events[event]; + result.push(elementListeners.element.addEventListener(event, listener, false)); + } + return result; + }(); + }); + } + + // Deactivates all listeners stored in @listeners + + }, { + key: "removeEventListeners", + value: function removeEventListeners() { + return this.listeners.map(function (elementListeners) { + return function () { + var result = []; + for (var event in elementListeners.events) { + var listener = elementListeners.events[event]; + result.push(elementListeners.element.removeEventListener(event, listener, false)); + } + return result; + }(); + }); + } + + // Removes all event listeners and cancels all files in the queue or being processed. + + }, { + key: "disable", + value: function disable() { + var _this4 = this; + + this.clickableElements.forEach(function (element) { + return element.classList.remove("dz-clickable"); + }); + this.removeEventListeners(); + + return this.files.map(function (file) { + return _this4.cancelUpload(file); + }); + } + }, { + key: "enable", + value: function enable() { + this.clickableElements.forEach(function (element) { + return element.classList.add("dz-clickable"); + }); + return this.setupEventListeners(); + } + + // Returns a nicely formatted filesize + + }, { + key: "filesize", + value: function filesize(size) { + var selectedSize = 0; + var selectedUnit = "b"; + + if (size > 0) { + var units = ['tb', 'gb', 'mb', 'kb', 'b']; + + for (var i = 0; i < units.length; i++) { + var unit = units[i]; + var cutoff = Math.pow(this.options.filesizeBase, 4 - i) / 10; + + if (size >= cutoff) { + selectedSize = size / Math.pow(this.options.filesizeBase, 4 - i); + selectedUnit = unit; + break; + } + } + + selectedSize = Math.round(10 * selectedSize) / 10; // Cutting of digits + } + + return "" + selectedSize + " " + this.options.dictFileSizeUnits[selectedUnit]; + } + + // Adds or removes the `dz-max-files-reached` class from the form. + + }, { + key: "_updateMaxFilesReachedClass", + value: function _updateMaxFilesReachedClass() { + if (this.options.maxFiles != null && this.getAcceptedFiles().length >= this.options.maxFiles) { + if (this.getAcceptedFiles().length === this.options.maxFiles) { + this.emit('maxfilesreached', this.files); + } + return this.element.classList.add("dz-max-files-reached"); + } else { + return this.element.classList.remove("dz-max-files-reached"); + } + } + }, { + key: "drop", + value: function drop(e) { + if (!e.dataTransfer) { + return; + } + this.emit("drop", e); + + var files = e.dataTransfer.files; + + this.emit("addedfiles", files); + + // Even if it's a folder, files.length will contain the folders. + if (files.length) { + var items = e.dataTransfer.items; + + if (items && items.length && items[0].webkitGetAsEntry != null) { + // The browser supports dropping of folders, so handle items instead of files + this._addFilesFromItems(items); + } else { + this.handleFiles(files); + } + } + } + }, { + key: "paste", + value: function paste(e) { + if (__guard__(e != null ? e.clipboardData : undefined, function (x) { + return x.items; + }) == null) { + return; + } + + this.emit("paste", e); + var items = e.clipboardData.items; + + + if (items.length) { + return this._addFilesFromItems(items); + } + } + }, { + key: "handleFiles", + value: function handleFiles(files) { + var _this5 = this; + + return files.map(function (file) { + return _this5.addFile(file); + }); + } + + // When a folder is dropped (or files are pasted), items must be handled + // instead of files. + + }, { + key: "_addFilesFromItems", + value: function _addFilesFromItems(items) { + var _this6 = this; + + return function () { + var result = []; + for (var _iterator14 = items, _isArray14 = true, _i15 = 0, _iterator14 = _isArray14 ? _iterator14 : _iterator14[Symbol.iterator](); ;) { + var _ref13; + + if (_isArray14) { + if (_i15 >= _iterator14.length) break; + _ref13 = _iterator14[_i15++]; + } else { + _i15 = _iterator14.next(); + if (_i15.done) break; + _ref13 = _i15.value; + } + + var item = _ref13; + + var entry; + if (item.webkitGetAsEntry != null && (entry = item.webkitGetAsEntry())) { + if (entry.isFile) { + result.push(_this6.addFile(item.getAsFile())); + } else if (entry.isDirectory) { + // Append all files from that directory to files + result.push(_this6._addFilesFromDirectory(entry, entry.name)); + } else { + result.push(undefined); + } + } else if (item.getAsFile != null) { + if (item.kind == null || item.kind === "file") { + result.push(_this6.addFile(item.getAsFile())); + } else { + result.push(undefined); + } + } else { + result.push(undefined); + } + } + return result; + }(); + } + + // Goes through the directory, and adds each file it finds recursively + + }, { + key: "_addFilesFromDirectory", + value: function _addFilesFromDirectory(directory, path) { + var _this7 = this; + + var dirReader = directory.createReader(); + + var errorHandler = function errorHandler(error) { + return __guardMethod__(console, 'log', function (o) { + return o.log(error); + }); + }; + + var readEntries = function readEntries() { + return dirReader.readEntries(function (entries) { + if (entries.length > 0) { + for (var _iterator15 = entries, _isArray15 = true, _i16 = 0, _iterator15 = _isArray15 ? _iterator15 : _iterator15[Symbol.iterator](); ;) { + var _ref14; + + if (_isArray15) { + if (_i16 >= _iterator15.length) break; + _ref14 = _iterator15[_i16++]; + } else { + _i16 = _iterator15.next(); + if (_i16.done) break; + _ref14 = _i16.value; + } + + var entry = _ref14; + + if (entry.isFile) { + entry.file(function (file) { + if (_this7.options.ignoreHiddenFiles && file.name.substring(0, 1) === '.') { + return; + } + file.fullPath = path + "/" + file.name; + return _this7.addFile(file); + }); + } else if (entry.isDirectory) { + _this7._addFilesFromDirectory(entry, path + "/" + entry.name); + } + } + + // Recursively call readEntries() again, since browser only handle + // the first 100 entries. + // See: https://developer.mozilla.org/en-US/docs/Web/API/DirectoryReader#readEntries + readEntries(); + } + return null; + }, errorHandler); + }; + + return readEntries(); + } + + // If `done()` is called without argument the file is accepted + // If you call it with an error message, the file is rejected + // (This allows for asynchronous validation) + // + // This function checks the filesize, and if the file.type passes the + // `acceptedFiles` check. + + }, { + key: "accept", + value: function accept(file, done) { + if (file.size > this.options.maxFilesize * 1024 * 1024) { + return done(this.options.dictFileTooBig.replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100).replace("{{maxFilesize}}", this.options.maxFilesize)); + } else if (!Dropzone.isValidFile(file, this.options.acceptedFiles)) { + return done(this.options.dictInvalidFileType); + } else if (this.options.maxFiles != null && this.getAcceptedFiles().length >= this.options.maxFiles) { + done(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}", this.options.maxFiles)); + return this.emit("maxfilesexceeded", file); + } else { + return this.options.accept.call(this, file, done); + } + } + }, { + key: "addFile", + value: function addFile(file) { + var _this8 = this; + + file.upload = { + uuid: Dropzone.uuidv4(), + progress: 0, + // Setting the total upload size to file.size for the beginning + // It's actual different than the size to be transmitted. + total: file.size, + bytesSent: 0, + filename: this._renameFile(file), + chunked: this.options.chunking && (this.options.forceChunking || file.size > this.options.chunkSize), + totalChunkCount: Math.ceil(file.size / this.options.chunkSize) + }; + this.files.push(file); + + file.status = Dropzone.ADDED; + + this.emit("addedfile", file); + + this._enqueueThumbnail(file); + + return this.accept(file, function (error) { + if (error) { + file.accepted = false; + _this8._errorProcessing([file], error); // Will set the file.status + } else { + file.accepted = true; + if (_this8.options.autoQueue) { + _this8.enqueueFile(file); + } // Will set .accepted = true + } + return _this8._updateMaxFilesReachedClass(); + }); + } + + // Wrapper for enqueueFile + + }, { + key: "enqueueFiles", + value: function enqueueFiles(files) { + for (var _iterator16 = files, _isArray16 = true, _i17 = 0, _iterator16 = _isArray16 ? _iterator16 : _iterator16[Symbol.iterator](); ;) { + var _ref15; + + if (_isArray16) { + if (_i17 >= _iterator16.length) break; + _ref15 = _iterator16[_i17++]; + } else { + _i17 = _iterator16.next(); + if (_i17.done) break; + _ref15 = _i17.value; + } + + var file = _ref15; + + this.enqueueFile(file); + } + return null; + } + }, { + key: "enqueueFile", + value: function enqueueFile(file) { + var _this9 = this; + + if (file.status === Dropzone.ADDED && file.accepted === true) { + file.status = Dropzone.QUEUED; + if (this.options.autoProcessQueue) { + return setTimeout(function () { + return _this9.processQueue(); + }, 0); // Deferring the call + } + } else { + throw new Error("This file can't be queued because it has already been processed or was rejected."); + } + } + }, { + key: "_enqueueThumbnail", + value: function _enqueueThumbnail(file) { + var _this10 = this; + + if (this.options.createImageThumbnails && file.type.match(/image.*/) && file.size <= this.options.maxThumbnailFilesize * 1024 * 1024) { + this._thumbnailQueue.push(file); + return setTimeout(function () { + return _this10._processThumbnailQueue(); + }, 0); // Deferring the call + } + } + }, { + key: "_processThumbnailQueue", + value: function _processThumbnailQueue() { + var _this11 = this; + + if (this._processingThumbnail || this._thumbnailQueue.length === 0) { + return; + } + + this._processingThumbnail = true; + var file = this._thumbnailQueue.shift(); + return this.createThumbnail(file, this.options.thumbnailWidth, this.options.thumbnailHeight, this.options.thumbnailMethod, true, function (dataUrl) { + _this11.emit("thumbnail", file, dataUrl); + _this11._processingThumbnail = false; + return _this11._processThumbnailQueue(); + }); + } + + // Can be called by the user to remove a file + + }, { + key: "removeFile", + value: function removeFile(file) { + if (file.status === Dropzone.UPLOADING) { + this.cancelUpload(file); + } + this.files = without(this.files, file); + + this.emit("removedfile", file); + if (this.files.length === 0) { + return this.emit("reset"); + } + } + + // Removes all files that aren't currently processed from the list + + }, { + key: "removeAllFiles", + value: function removeAllFiles(cancelIfNecessary) { + // Create a copy of files since removeFile() changes the @files array. + if (cancelIfNecessary == null) { + cancelIfNecessary = false; + } + for (var _iterator17 = this.files.slice(), _isArray17 = true, _i18 = 0, _iterator17 = _isArray17 ? _iterator17 : _iterator17[Symbol.iterator](); ;) { + var _ref16; + + if (_isArray17) { + if (_i18 >= _iterator17.length) break; + _ref16 = _iterator17[_i18++]; + } else { + _i18 = _iterator17.next(); + if (_i18.done) break; + _ref16 = _i18.value; + } + + var file = _ref16; + + if (file.status !== Dropzone.UPLOADING || cancelIfNecessary) { + this.removeFile(file); + } + } + return null; + } + + // Resizes an image before it gets sent to the server. This function is the default behavior of + // `options.transformFile` if `resizeWidth` or `resizeHeight` are set. The callback is invoked with + // the resized blob. + + }, { + key: "resizeImage", + value: function resizeImage(file, width, height, resizeMethod, callback) { + var _this12 = this; + + return this.createThumbnail(file, width, height, resizeMethod, false, function (dataUrl, canvas) { + if (canvas === null) { + // The image has not been resized + return callback(file); + } else { + var resizeMimeType = _this12.options.resizeMimeType; + + if (resizeMimeType == null) { + resizeMimeType = file.type; + } + var resizedDataURL = canvas.toDataURL(resizeMimeType, _this12.options.resizeQuality); + if (resizeMimeType === 'image/jpeg' || resizeMimeType === 'image/jpg') { + // Now add the original EXIF information + resizedDataURL = ExifRestore.restore(file.dataURL, resizedDataURL); + } + return callback(Dropzone.dataURItoBlob(resizedDataURL)); + } + }); + } + }, { + key: "createThumbnail", + value: function createThumbnail(file, width, height, resizeMethod, fixOrientation, callback) { + var _this13 = this; + + var fileReader = new FileReader(); + + fileReader.onload = function () { + + file.dataURL = fileReader.result; + + // Don't bother creating a thumbnail for SVG images since they're vector + if (file.type === "image/svg+xml") { + if (callback != null) { + callback(fileReader.result); + } + return; + } + + return _this13.createThumbnailFromUrl(file, width, height, resizeMethod, fixOrientation, callback); + }; + + return fileReader.readAsDataURL(file); + } + }, { + key: "createThumbnailFromUrl", + value: function createThumbnailFromUrl(file, width, height, resizeMethod, fixOrientation, callback, crossOrigin) { + var _this14 = this; + + // Not using `new Image` here because of a bug in latest Chrome versions. + // See https://github.com/enyo/dropzone/pull/226 + var img = document.createElement("img"); + + if (crossOrigin) { + img.crossOrigin = crossOrigin; + } + + img.onload = function () { + var loadExif = function loadExif(callback) { + return callback(1); + }; + if (typeof EXIF !== 'undefined' && EXIF !== null && fixOrientation) { + loadExif = function loadExif(callback) { + return EXIF.getData(img, function () { + return callback(EXIF.getTag(this, 'Orientation')); + }); + }; + } + + return loadExif(function (orientation) { + file.width = img.width; + file.height = img.height; + + var resizeInfo = _this14.options.resize.call(_this14, file, width, height, resizeMethod); + + var canvas = document.createElement("canvas"); + var ctx = canvas.getContext("2d"); + + canvas.width = resizeInfo.trgWidth; + canvas.height = resizeInfo.trgHeight; + + if (orientation > 4) { + canvas.width = resizeInfo.trgHeight; + canvas.height = resizeInfo.trgWidth; + } + + switch (orientation) { + case 2: + // horizontal flip + ctx.translate(canvas.width, 0); + ctx.scale(-1, 1); + break; + case 3: + // 180° rotate left + ctx.translate(canvas.width, canvas.height); + ctx.rotate(Math.PI); + break; + case 4: + // vertical flip + ctx.translate(0, canvas.height); + ctx.scale(1, -1); + break; + case 5: + // vertical flip + 90 rotate right + ctx.rotate(0.5 * Math.PI); + ctx.scale(1, -1); + break; + case 6: + // 90° rotate right + ctx.rotate(0.5 * Math.PI); + ctx.translate(0, -canvas.height); + break; + case 7: + // horizontal flip + 90 rotate right + ctx.rotate(0.5 * Math.PI); + ctx.translate(canvas.width, -canvas.height); + ctx.scale(-1, 1); + break; + case 8: + // 90° rotate left + ctx.rotate(-0.5 * Math.PI); + ctx.translate(-canvas.width, 0); + break; + } + + // This is a bugfix for iOS' scaling bug. + drawImageIOSFix(ctx, img, resizeInfo.srcX != null ? resizeInfo.srcX : 0, resizeInfo.srcY != null ? resizeInfo.srcY : 0, resizeInfo.srcWidth, resizeInfo.srcHeight, resizeInfo.trgX != null ? resizeInfo.trgX : 0, resizeInfo.trgY != null ? resizeInfo.trgY : 0, resizeInfo.trgWidth, resizeInfo.trgHeight); + + var thumbnail = canvas.toDataURL("image/png"); + + if (callback != null) { + return callback(thumbnail, canvas); + } + }); + }; + + if (callback != null) { + img.onerror = callback; + } + + return img.src = file.dataURL; + } + + // Goes through the queue and processes files if there aren't too many already. + + }, { + key: "processQueue", + value: function processQueue() { + var parallelUploads = this.options.parallelUploads; + + var processingLength = this.getUploadingFiles().length; + var i = processingLength; + + // There are already at least as many files uploading than should be + if (processingLength >= parallelUploads) { + return; + } + + var queuedFiles = this.getQueuedFiles(); + + if (!(queuedFiles.length > 0)) { + return; + } + + if (this.options.uploadMultiple) { + // The files should be uploaded in one request + return this.processFiles(queuedFiles.slice(0, parallelUploads - processingLength)); + } else { + while (i < parallelUploads) { + if (!queuedFiles.length) { + return; + } // Nothing left to process + this.processFile(queuedFiles.shift()); + i++; + } + } + } + + // Wrapper for `processFiles` + + }, { + key: "processFile", + value: function processFile(file) { + return this.processFiles([file]); + } + + // Loads the file, then calls finishedLoading() + + }, { + key: "processFiles", + value: function processFiles(files) { + for (var _iterator18 = files, _isArray18 = true, _i19 = 0, _iterator18 = _isArray18 ? _iterator18 : _iterator18[Symbol.iterator](); ;) { + var _ref17; + + if (_isArray18) { + if (_i19 >= _iterator18.length) break; + _ref17 = _iterator18[_i19++]; + } else { + _i19 = _iterator18.next(); + if (_i19.done) break; + _ref17 = _i19.value; + } + + var file = _ref17; + + file.processing = true; // Backwards compatibility + file.status = Dropzone.UPLOADING; + + this.emit("processing", file); + } + + if (this.options.uploadMultiple) { + this.emit("processingmultiple", files); + } + + return this.uploadFiles(files); + } + }, { + key: "_getFilesWithXhr", + value: function _getFilesWithXhr(xhr) { + var files = void 0; + return files = this.files.filter(function (file) { + return file.xhr === xhr; + }).map(function (file) { + return file; + }); + } + + // Cancels the file upload and sets the status to CANCELED + // **if** the file is actually being uploaded. + // If it's still in the queue, the file is being removed from it and the status + // set to CANCELED. + + }, { + key: "cancelUpload", + value: function cancelUpload(file) { + if (file.status === Dropzone.UPLOADING) { + var groupedFiles = this._getFilesWithXhr(file.xhr); + for (var _iterator19 = groupedFiles, _isArray19 = true, _i20 = 0, _iterator19 = _isArray19 ? _iterator19 : _iterator19[Symbol.iterator](); ;) { + var _ref18; + + if (_isArray19) { + if (_i20 >= _iterator19.length) break; + _ref18 = _iterator19[_i20++]; + } else { + _i20 = _iterator19.next(); + if (_i20.done) break; + _ref18 = _i20.value; + } + + var groupedFile = _ref18; + + groupedFile.status = Dropzone.CANCELED; + } + if (typeof file.xhr !== 'undefined') { + file.xhr.abort(); + } + for (var _iterator20 = groupedFiles, _isArray20 = true, _i21 = 0, _iterator20 = _isArray20 ? _iterator20 : _iterator20[Symbol.iterator](); ;) { + var _ref19; + + if (_isArray20) { + if (_i21 >= _iterator20.length) break; + _ref19 = _iterator20[_i21++]; + } else { + _i21 = _iterator20.next(); + if (_i21.done) break; + _ref19 = _i21.value; + } + + var _groupedFile = _ref19; + + this.emit("canceled", _groupedFile); + } + if (this.options.uploadMultiple) { + this.emit("canceledmultiple", groupedFiles); + } + } else if (file.status === Dropzone.ADDED || file.status === Dropzone.QUEUED) { + file.status = Dropzone.CANCELED; + this.emit("canceled", file); + if (this.options.uploadMultiple) { + this.emit("canceledmultiple", [file]); + } + } + + if (this.options.autoProcessQueue) { + return this.processQueue(); + } + } + }, { + key: "resolveOption", + value: function resolveOption(option) { + if (typeof option === 'function') { + for (var _len3 = arguments.length, args = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { + args[_key3 - 1] = arguments[_key3]; + } + + return option.apply(this, args); + } + return option; + } + }, { + key: "uploadFile", + value: function uploadFile(file) { + return this.uploadFiles([file]); + } + }, { + key: "uploadFiles", + value: function uploadFiles(files) { + var _this15 = this; + + this._transformFiles(files, function (transformedFiles) { + if (files[0].upload.chunked) { + // This file should be sent in chunks! + + // If the chunking option is set, we **know** that there can only be **one** file, since + // uploadMultiple is not allowed with this option. + var file = files[0]; + var transformedFile = transformedFiles[0]; + var startedChunkCount = 0; + + file.upload.chunks = []; + + var handleNextChunk = function handleNextChunk() { + var chunkIndex = 0; + + // Find the next item in file.upload.chunks that is not defined yet. + while (file.upload.chunks[chunkIndex] !== undefined) { + chunkIndex++; + } + + // This means, that all chunks have already been started. + if (chunkIndex >= file.upload.totalChunkCount) return; + + startedChunkCount++; + + var start = chunkIndex * _this15.options.chunkSize; + var end = Math.min(start + _this15.options.chunkSize, file.size); + + var dataBlock = { + name: _this15._getParamName(0), + data: transformedFile.webkitSlice ? transformedFile.webkitSlice(start, end) : transformedFile.slice(start, end), + filename: file.upload.filename, + chunkIndex: chunkIndex + }; + + file.upload.chunks[chunkIndex] = { + file: file, + index: chunkIndex, + dataBlock: dataBlock, // In case we want to retry. + status: Dropzone.UPLOADING, + progress: 0, + retries: 0 // The number of times this block has been retried. + }; + + _this15._uploadData(files, [dataBlock]); + }; + + file.upload.finishedChunkUpload = function (chunk) { + var allFinished = true; + chunk.status = Dropzone.SUCCESS; + + // Clear the data from the chunk + chunk.dataBlock = null; + + for (var i = 0; i < file.upload.totalChunkCount; i++) { + if (file.upload.chunks[i] === undefined) { + return handleNextChunk(); + } + if (file.upload.chunks[i].status !== Dropzone.SUCCESS) { + allFinished = false; + } + } + + if (allFinished) { + _this15.options.chunksUploaded(file, function () { + _this15._finished(files, '', null); + }); + } + }; + + if (_this15.options.parallelChunkUploads) { + for (var i = 0; i < file.upload.totalChunkCount; i++) { + handleNextChunk(); + } + } else { + handleNextChunk(); + } + } else { + var dataBlocks = []; + for (var _i22 = 0; _i22 < files.length; _i22++) { + dataBlocks[_i22] = { + name: _this15._getParamName(_i22), + data: transformedFiles[_i22], + filename: files[_i22].upload.filename + }; + } + _this15._uploadData(files, dataBlocks); + } + }); + } + + /// Returns the right chunk for given file and xhr + + }, { + key: "_getChunk", + value: function _getChunk(file, xhr) { + for (var i = 0; i < file.upload.totalChunkCount; i++) { + if (file.upload.chunks[i] !== undefined && file.upload.chunks[i].xhr === xhr) { + return file.upload.chunks[i]; + } + } + } + + // This function actually uploads the file(s) to the server. + // If dataBlocks contains the actual data to upload (meaning, that this could either be transformed + // files, or individual chunks for chunked upload). + + }, { + key: "_uploadData", + value: function _uploadData(files, dataBlocks) { + var _this16 = this; + + var xhr = new XMLHttpRequest(); + + // Put the xhr object in the file objects to be able to reference it later. + for (var _iterator21 = files, _isArray21 = true, _i23 = 0, _iterator21 = _isArray21 ? _iterator21 : _iterator21[Symbol.iterator](); ;) { + var _ref20; + + if (_isArray21) { + if (_i23 >= _iterator21.length) break; + _ref20 = _iterator21[_i23++]; + } else { + _i23 = _iterator21.next(); + if (_i23.done) break; + _ref20 = _i23.value; + } + + var file = _ref20; + + file.xhr = xhr; + } + if (files[0].upload.chunked) { + // Put the xhr object in the right chunk object, so it can be associated later, and found with _getChunk + files[0].upload.chunks[dataBlocks[0].chunkIndex].xhr = xhr; + } + + var method = this.resolveOption(this.options.method, files); + var url = this.resolveOption(this.options.url, files); + xhr.open(method, url, true); + + // Setting the timeout after open because of IE11 issue: https://gitlab.com/meno/dropzone/issues/8 + xhr.timeout = this.resolveOption(this.options.timeout, files); + + // Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179 + xhr.withCredentials = !!this.options.withCredentials; + + xhr.onload = function (e) { + _this16._finishedUploading(files, xhr, e); + }; + + xhr.onerror = function () { + _this16._handleUploadError(files, xhr); + }; + + // Some browsers do not have the .upload property + var progressObj = xhr.upload != null ? xhr.upload : xhr; + progressObj.onprogress = function (e) { + return _this16._updateFilesUploadProgress(files, xhr, e); + }; + + var headers = { + "Accept": "application/json", + "Cache-Control": "no-cache", + "X-Requested-With": "XMLHttpRequest" + }; + + if (this.options.headers) { + Dropzone.extend(headers, this.options.headers); + } + + for (var headerName in headers) { + var headerValue = headers[headerName]; + if (headerValue) { + xhr.setRequestHeader(headerName, headerValue); + } + } + + var formData = new FormData(); + + // Adding all @options parameters + if (this.options.params) { + var additionalParams = this.options.params; + if (typeof additionalParams === 'function') { + additionalParams = additionalParams.call(this, files, xhr, files[0].upload.chunked ? this._getChunk(files[0], xhr) : null); + } + + for (var key in additionalParams) { + var value = additionalParams[key]; + formData.append(key, value); + } + } + + // Let the user add additional data if necessary + for (var _iterator22 = files, _isArray22 = true, _i24 = 0, _iterator22 = _isArray22 ? _iterator22 : _iterator22[Symbol.iterator](); ;) { + var _ref21; + + if (_isArray22) { + if (_i24 >= _iterator22.length) break; + _ref21 = _iterator22[_i24++]; + } else { + _i24 = _iterator22.next(); + if (_i24.done) break; + _ref21 = _i24.value; + } + + var _file = _ref21; + + this.emit("sending", _file, xhr, formData); + } + if (this.options.uploadMultiple) { + this.emit("sendingmultiple", files, xhr, formData); + } + + this._addFormElementData(formData); + + // Finally add the files + // Has to be last because some servers (eg: S3) expect the file to be the last parameter + for (var i = 0; i < dataBlocks.length; i++) { + var dataBlock = dataBlocks[i]; + formData.append(dataBlock.name, dataBlock.data, dataBlock.filename); + } + + this.submitRequest(xhr, formData, files); + } + + // Transforms all files with this.options.transformFile and invokes done with the transformed files when done. + + }, { + key: "_transformFiles", + value: function _transformFiles(files, done) { + var _this17 = this; + + var transformedFiles = []; + // Clumsy way of handling asynchronous calls, until I get to add a proper Future library. + var doneCounter = 0; + + var _loop = function _loop(i) { + _this17.options.transformFile.call(_this17, files[i], function (transformedFile) { + transformedFiles[i] = transformedFile; + if (++doneCounter === files.length) { + done(transformedFiles); + } + }); + }; + + for (var i = 0; i < files.length; i++) { + _loop(i); + } + } + + // Takes care of adding other input elements of the form to the AJAX request + + }, { + key: "_addFormElementData", + value: function _addFormElementData(formData) { + // Take care of other input elements + if (this.element.tagName === "FORM") { + for (var _iterator23 = this.element.querySelectorAll("input, textarea, select, button"), _isArray23 = true, _i25 = 0, _iterator23 = _isArray23 ? _iterator23 : _iterator23[Symbol.iterator](); ;) { + var _ref22; + + if (_isArray23) { + if (_i25 >= _iterator23.length) break; + _ref22 = _iterator23[_i25++]; + } else { + _i25 = _iterator23.next(); + if (_i25.done) break; + _ref22 = _i25.value; + } + + var input = _ref22; + + var inputName = input.getAttribute("name"); + var inputType = input.getAttribute("type"); + if (inputType) inputType = inputType.toLowerCase(); + + // If the input doesn't have a name, we can't use it. + if (typeof inputName === 'undefined' || inputName === null) continue; + + if (input.tagName === "SELECT" && input.hasAttribute("multiple")) { + // Possibly multiple values + for (var _iterator24 = input.options, _isArray24 = true, _i26 = 0, _iterator24 = _isArray24 ? _iterator24 : _iterator24[Symbol.iterator](); ;) { + var _ref23; + + if (_isArray24) { + if (_i26 >= _iterator24.length) break; + _ref23 = _iterator24[_i26++]; + } else { + _i26 = _iterator24.next(); + if (_i26.done) break; + _ref23 = _i26.value; + } + + var option = _ref23; + + if (option.selected) { + formData.append(inputName, option.value); + } + } + } else if (!inputType || inputType !== "checkbox" && inputType !== "radio" || input.checked) { + formData.append(inputName, input.value); + } + } + } + } + + // Invoked when there is new progress information about given files. + // If e is not provided, it is assumed that the upload is finished. + + }, { + key: "_updateFilesUploadProgress", + value: function _updateFilesUploadProgress(files, xhr, e) { + var progress = void 0; + if (typeof e !== 'undefined') { + progress = 100 * e.loaded / e.total; + + if (files[0].upload.chunked) { + var file = files[0]; + // Since this is a chunked upload, we need to update the appropriate chunk progress. + var chunk = this._getChunk(file, xhr); + chunk.progress = progress; + chunk.total = e.total; + chunk.bytesSent = e.loaded; + var fileProgress = 0, + fileTotal = void 0, + fileBytesSent = void 0; + file.upload.progress = 0; + file.upload.total = 0; + file.upload.bytesSent = 0; + for (var i = 0; i < file.upload.totalChunkCount; i++) { + if (file.upload.chunks[i] !== undefined && file.upload.chunks[i].progress !== undefined) { + file.upload.progress += file.upload.chunks[i].progress; + file.upload.total += file.upload.chunks[i].total; + file.upload.bytesSent += file.upload.chunks[i].bytesSent; + } + } + file.upload.progress = file.upload.progress / file.upload.totalChunkCount; + } else { + for (var _iterator25 = files, _isArray25 = true, _i27 = 0, _iterator25 = _isArray25 ? _iterator25 : _iterator25[Symbol.iterator](); ;) { + var _ref24; + + if (_isArray25) { + if (_i27 >= _iterator25.length) break; + _ref24 = _iterator25[_i27++]; + } else { + _i27 = _iterator25.next(); + if (_i27.done) break; + _ref24 = _i27.value; + } + + var _file2 = _ref24; + + _file2.upload.progress = progress; + _file2.upload.total = e.total; + _file2.upload.bytesSent = e.loaded; + } + } + for (var _iterator26 = files, _isArray26 = true, _i28 = 0, _iterator26 = _isArray26 ? _iterator26 : _iterator26[Symbol.iterator](); ;) { + var _ref25; + + if (_isArray26) { + if (_i28 >= _iterator26.length) break; + _ref25 = _iterator26[_i28++]; + } else { + _i28 = _iterator26.next(); + if (_i28.done) break; + _ref25 = _i28.value; + } + + var _file3 = _ref25; + + this.emit("uploadprogress", _file3, _file3.upload.progress, _file3.upload.bytesSent); + } + } else { + // Called when the file finished uploading + + var allFilesFinished = true; + + progress = 100; + + for (var _iterator27 = files, _isArray27 = true, _i29 = 0, _iterator27 = _isArray27 ? _iterator27 : _iterator27[Symbol.iterator](); ;) { + var _ref26; + + if (_isArray27) { + if (_i29 >= _iterator27.length) break; + _ref26 = _iterator27[_i29++]; + } else { + _i29 = _iterator27.next(); + if (_i29.done) break; + _ref26 = _i29.value; + } + + var _file4 = _ref26; + + if (_file4.upload.progress !== 100 || _file4.upload.bytesSent !== _file4.upload.total) { + allFilesFinished = false; + } + _file4.upload.progress = progress; + _file4.upload.bytesSent = _file4.upload.total; + } + + // Nothing to do, all files already at 100% + if (allFilesFinished) { + return; + } + + for (var _iterator28 = files, _isArray28 = true, _i30 = 0, _iterator28 = _isArray28 ? _iterator28 : _iterator28[Symbol.iterator](); ;) { + var _ref27; + + if (_isArray28) { + if (_i30 >= _iterator28.length) break; + _ref27 = _iterator28[_i30++]; + } else { + _i30 = _iterator28.next(); + if (_i30.done) break; + _ref27 = _i30.value; + } + + var _file5 = _ref27; + + this.emit("uploadprogress", _file5, progress, _file5.upload.bytesSent); + } + } + } + }, { + key: "_finishedUploading", + value: function _finishedUploading(files, xhr, e) { + var response = void 0; + + if (files[0].status === Dropzone.CANCELED) { + return; + } + + if (xhr.readyState !== 4) { + return; + } + + if (xhr.responseType !== 'arraybuffer' && xhr.responseType !== 'blob') { + response = xhr.responseText; + + if (xhr.getResponseHeader("content-type") && ~xhr.getResponseHeader("content-type").indexOf("application/json")) { + try { + response = JSON.parse(response); + } catch (error) { + e = error; + response = "Invalid JSON response from server."; + } + } + } + + this._updateFilesUploadProgress(files); + + if (!(200 <= xhr.status && xhr.status < 300)) { + this._handleUploadError(files, xhr, response); + } else { + if (files[0].upload.chunked) { + files[0].upload.finishedChunkUpload(this._getChunk(files[0], xhr)); + } else { + this._finished(files, response, e); + } + } + } + }, { + key: "_handleUploadError", + value: function _handleUploadError(files, xhr, response) { + if (files[0].status === Dropzone.CANCELED) { + return; + } + + if (files[0].upload.chunked && this.options.retryChunks) { + var chunk = this._getChunk(files[0], xhr); + if (chunk.retries++ < this.options.retryChunksLimit) { + this._uploadData(files, [chunk.dataBlock]); + return; + } else { + console.warn('Retried this chunk too often. Giving up.'); + } + } + + for (var _iterator29 = files, _isArray29 = true, _i31 = 0, _iterator29 = _isArray29 ? _iterator29 : _iterator29[Symbol.iterator](); ;) { + var _ref28; + + if (_isArray29) { + if (_i31 >= _iterator29.length) break; + _ref28 = _iterator29[_i31++]; + } else { + _i31 = _iterator29.next(); + if (_i31.done) break; + _ref28 = _i31.value; + } + + var file = _ref28; + + this._errorProcessing(files, response || this.options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr); + } + } + }, { + key: "submitRequest", + value: function submitRequest(xhr, formData, files) { + xhr.send(formData); + } + + // Called internally when processing is finished. + // Individual callbacks have to be called in the appropriate sections. + + }, { + key: "_finished", + value: function _finished(files, responseText, e) { + for (var _iterator30 = files, _isArray30 = true, _i32 = 0, _iterator30 = _isArray30 ? _iterator30 : _iterator30[Symbol.iterator](); ;) { + var _ref29; + + if (_isArray30) { + if (_i32 >= _iterator30.length) break; + _ref29 = _iterator30[_i32++]; + } else { + _i32 = _iterator30.next(); + if (_i32.done) break; + _ref29 = _i32.value; + } + + var file = _ref29; + + file.status = Dropzone.SUCCESS; + this.emit("success", file, responseText, e); + this.emit("complete", file); + } + if (this.options.uploadMultiple) { + this.emit("successmultiple", files, responseText, e); + this.emit("completemultiple", files); + } + + if (this.options.autoProcessQueue) { + return this.processQueue(); + } + } + + // Called internally when processing is finished. + // Individual callbacks have to be called in the appropriate sections. + + }, { + key: "_errorProcessing", + value: function _errorProcessing(files, message, xhr) { + for (var _iterator31 = files, _isArray31 = true, _i33 = 0, _iterator31 = _isArray31 ? _iterator31 : _iterator31[Symbol.iterator](); ;) { + var _ref30; + + if (_isArray31) { + if (_i33 >= _iterator31.length) break; + _ref30 = _iterator31[_i33++]; + } else { + _i33 = _iterator31.next(); + if (_i33.done) break; + _ref30 = _i33.value; + } + + var file = _ref30; + + file.status = Dropzone.ERROR; + this.emit("error", file, message, xhr); + this.emit("complete", file); + } + if (this.options.uploadMultiple) { + this.emit("errormultiple", files, message, xhr); + this.emit("completemultiple", files); + } + + if (this.options.autoProcessQueue) { + return this.processQueue(); + } + } + }], [{ + key: "uuidv4", + value: function uuidv4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0, + v = c === 'x' ? r : r & 0x3 | 0x8; + return v.toString(16); + }); + } + }]); + + return Dropzone; +}(Emitter); + +Dropzone.initClass(); + +Dropzone.version = "5.2.0"; + +// This is a map of options for your different dropzones. Add configurations +// to this object for your different dropzone elemens. +// +// Example: +// +// Dropzone.options.myDropzoneElementId = { maxFilesize: 1 }; +// +// To disable autoDiscover for a specific element, you can set `false` as an option: +// +// Dropzone.options.myDisabledElementId = false; +// +// And in html: +// +//
+Dropzone.options = {}; + +// Returns the options for an element or undefined if none available. +Dropzone.optionsForElement = function (element) { + // Get the `Dropzone.options.elementId` for this element if it exists + if (element.getAttribute("id")) { + return Dropzone.options[camelize(element.getAttribute("id"))]; + } else { + return undefined; + } +}; + +// Holds a list of all dropzone instances +Dropzone.instances = []; + +// Returns the dropzone for given element if any +Dropzone.forElement = function (element) { + if (typeof element === "string") { + element = document.querySelector(element); + } + if ((element != null ? element.dropzone : undefined) == null) { + throw new Error("No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone."); + } + return element.dropzone; +}; + +// Set to false if you don't want Dropzone to automatically find and attach to .dropzone elements. +Dropzone.autoDiscover = true; + +// Looks for all .dropzone elements and creates a dropzone for them +Dropzone.discover = function () { + var dropzones = void 0; + if (document.querySelectorAll) { + dropzones = document.querySelectorAll(".dropzone"); + } else { + dropzones = []; + // IE :( + var checkElements = function checkElements(elements) { + return function () { + var result = []; + for (var _iterator32 = elements, _isArray32 = true, _i34 = 0, _iterator32 = _isArray32 ? _iterator32 : _iterator32[Symbol.iterator](); ;) { + var _ref31; + + if (_isArray32) { + if (_i34 >= _iterator32.length) break; + _ref31 = _iterator32[_i34++]; + } else { + _i34 = _iterator32.next(); + if (_i34.done) break; + _ref31 = _i34.value; + } + + var el = _ref31; + + if (/(^| )dropzone($| )/.test(el.className)) { + result.push(dropzones.push(el)); + } else { + result.push(undefined); + } + } + return result; + }(); + }; + checkElements(document.getElementsByTagName("div")); + checkElements(document.getElementsByTagName("form")); + } + + return function () { + var result = []; + for (var _iterator33 = dropzones, _isArray33 = true, _i35 = 0, _iterator33 = _isArray33 ? _iterator33 : _iterator33[Symbol.iterator](); ;) { + var _ref32; + + if (_isArray33) { + if (_i35 >= _iterator33.length) break; + _ref32 = _iterator33[_i35++]; + } else { + _i35 = _iterator33.next(); + if (_i35.done) break; + _ref32 = _i35.value; + } + + var dropzone = _ref32; + + // Create a dropzone unless auto discover has been disabled for specific element + if (Dropzone.optionsForElement(dropzone) !== false) { + result.push(new Dropzone(dropzone)); + } else { + result.push(undefined); + } + } + return result; + }(); +}; + +// Since the whole Drag'n'Drop API is pretty new, some browsers implement it, +// but not correctly. +// So I created a blacklist of userAgents. Yes, yes. Browser sniffing, I know. +// But what to do when browsers *theoretically* support an API, but crash +// when using it. +// +// This is a list of regular expressions tested against navigator.userAgent +// +// ** It should only be used on browser that *do* support the API, but +// incorrectly ** +// +Dropzone.blacklistedBrowsers = [ +// The mac os and windows phone version of opera 12 seems to have a problem with the File drag'n'drop API. + /opera.*(Macintosh|Windows Phone).*version\/12/i]; + +// Checks if the browser is supported +Dropzone.isBrowserSupported = function () { + var capableBrowser = true; + + if (window.File && window.FileReader && window.FileList && window.Blob && window.FormData && document.querySelector) { + if (!("classList" in document.createElement("a"))) { + capableBrowser = false; + } else { + // The browser supports the API, but may be blacklisted. + for (var _iterator34 = Dropzone.blacklistedBrowsers, _isArray34 = true, _i36 = 0, _iterator34 = _isArray34 ? _iterator34 : _iterator34[Symbol.iterator](); ;) { + var _ref33; + + if (_isArray34) { + if (_i36 >= _iterator34.length) break; + _ref33 = _iterator34[_i36++]; + } else { + _i36 = _iterator34.next(); + if (_i36.done) break; + _ref33 = _i36.value; + } + + var regex = _ref33; + + if (regex.test(navigator.userAgent)) { + capableBrowser = false; + + } + } + } + } else { + capableBrowser = false; + } + + return capableBrowser; +}; + +Dropzone.dataURItoBlob = function (dataURI) { + // convert base64 to raw binary data held in a string + // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this + var byteString = atob(dataURI.split(',')[1]); + + // separate out the mime component + var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; + + // write the bytes of the string to an ArrayBuffer + var ab = new ArrayBuffer(byteString.length); + var ia = new Uint8Array(ab); + for (var i = 0, end = byteString.length, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { + ia[i] = byteString.charCodeAt(i); + } + + // write the ArrayBuffer to a blob + return new Blob([ab], {type: mimeString}); +}; + +// Returns an array without the rejected item +var without = function without(list, rejectedItem) { + return list.filter(function (item) { + return item !== rejectedItem; + }).map(function (item) { + return item; + }); +}; + +// abc-def_ghi -> abcDefGhi +var camelize = function camelize(str) { + return str.replace(/[\-_](\w)/g, function (match) { + return match.charAt(1).toUpperCase(); + }); +}; + +// Creates an element from string +Dropzone.createElement = function (string) { + var div = document.createElement("div"); + div.innerHTML = string; + return div.childNodes[0]; +}; + +// Tests if given element is inside (or simply is) the container +Dropzone.elementInside = function (element, container) { + if (element === container) { + return true; + } // Coffeescript doesn't support do/while loops + while (element = element.parentNode) { + if (element === container) { + return true; + } + } + return false; +}; + +Dropzone.getElement = function (el, name) { + var element = void 0; + if (typeof el === "string") { + element = document.querySelector(el); + } else if (el.nodeType != null) { + element = el; + } + if (element == null) { + throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector or a plain HTML element."); + } + return element; +}; + +Dropzone.getElements = function (els, name) { + var el = void 0, + elements = void 0; + if (els instanceof Array) { + elements = []; + try { + for (var _iterator35 = els, _isArray35 = true, _i37 = 0, _iterator35 = _isArray35 ? _iterator35 : _iterator35[Symbol.iterator](); ;) { + if (_isArray35) { + if (_i37 >= _iterator35.length) break; + el = _iterator35[_i37++]; + } else { + _i37 = _iterator35.next(); + if (_i37.done) break; + el = _i37.value; + } + + elements.push(this.getElement(el, name)); + } + } catch (e) { + elements = null; + } + } else if (typeof els === "string") { + elements = []; + for (var _iterator36 = document.querySelectorAll(els), _isArray36 = true, _i38 = 0, _iterator36 = _isArray36 ? _iterator36 : _iterator36[Symbol.iterator](); ;) { + if (_isArray36) { + if (_i38 >= _iterator36.length) break; + el = _iterator36[_i38++]; + } else { + _i38 = _iterator36.next(); + if (_i38.done) break; + el = _i38.value; + } + + elements.push(el); + } + } else if (els.nodeType != null) { + elements = [els]; + } + + if (elements == null || !elements.length) { + throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector, a plain HTML element or a list of those."); + } + + return elements; +}; + +// Asks the user the question and calls accepted or rejected accordingly +// +// The default implementation just uses `window.confirm` and then calls the +// appropriate callback. +Dropzone.confirm = function (question, accepted, rejected) { + if (window.confirm(question)) { + return accepted(); + } else if (rejected != null) { + return rejected(); + } +}; + +// Validates the mime type like this: +// +// https://developer.mozilla.org/en-US/docs/HTML/Element/input#attr-accept +Dropzone.isValidFile = function (file, acceptedFiles) { + if (!acceptedFiles) { + return true; + } // If there are no accepted mime types, it's OK + acceptedFiles = acceptedFiles.split(","); + + var mimeType = file.type; + var baseMimeType = mimeType.replace(/\/.*$/, ""); + + for (var _iterator37 = acceptedFiles, _isArray37 = true, _i39 = 0, _iterator37 = _isArray37 ? _iterator37 : _iterator37[Symbol.iterator](); ;) { + var _ref34; + + if (_isArray37) { + if (_i39 >= _iterator37.length) break; + _ref34 = _iterator37[_i39++]; + } else { + _i39 = _iterator37.next(); + if (_i39.done) break; + _ref34 = _i39.value; + } + + var validType = _ref34; + + validType = validType.trim(); + if (validType.charAt(0) === ".") { + if (file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) !== -1) { + return true; + } + } else if (/\/\*$/.test(validType)) { + // This is something like a image/* mime type + if (baseMimeType === validType.replace(/\/.*$/, "")) { + return true; + } + } else { + if (mimeType === validType) { + return true; + } + } + } + + return false; +}; + +// Augment jQuery +if (typeof jQuery !== 'undefined' && jQuery !== null) { + jQuery.fn.dropzone = function (options) { + return this.each(function () { + return new Dropzone(this, options); + }); + }; +} + +if (typeof module !== 'undefined' && module !== null) { + module.exports = Dropzone; +} else { + window.Dropzone = Dropzone; +} + +// Dropzone file status codes +Dropzone.ADDED = "added"; + +Dropzone.QUEUED = "queued"; +// For backwards compatibility. Now, if a file is accepted, it's either queued +// or uploading. +Dropzone.ACCEPTED = Dropzone.QUEUED; + +Dropzone.UPLOADING = "uploading"; +Dropzone.PROCESSING = Dropzone.UPLOADING; // alias + +Dropzone.CANCELED = "canceled"; +Dropzone.ERROR = "error"; +Dropzone.SUCCESS = "success"; + +/* + + Bugfix for iOS 6 and 7 + Source: http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios + based on the work of https://github.com/stomita/ios-imagefile-megapixel + + */ + +// Detecting vertical squash in loaded image. +// Fixes a bug which squash image vertically while drawing into canvas for some images. +// This is a bug in iOS6 devices. This function from https://github.com/stomita/ios-imagefile-megapixel +var detectVerticalSquash = function detectVerticalSquash(img) { + var iw = img.naturalWidth; + var ih = img.naturalHeight; + var canvas = document.createElement("canvas"); + canvas.width = 1; + canvas.height = ih; + var ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0); + + var _ctx$getImageData = ctx.getImageData(1, 0, 1, ih), + data = _ctx$getImageData.data; + + // search image edge pixel position in case it is squashed vertically. + + + var sy = 0; + var ey = ih; + var py = ih; + while (py > sy) { + var alpha = data[(py - 1) * 4 + 3]; + + if (alpha === 0) { + ey = py; + } else { + sy = py; + } + + py = ey + sy >> 1; + } + var ratio = py / ih; + + if (ratio === 0) { + return 1; + } else { + return ratio; + } +}; + +// A replacement for context.drawImage +// (args are for source and destination). +var drawImageIOSFix = function drawImageIOSFix(ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) { + var vertSquashRatio = detectVerticalSquash(img); + return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio); +}; + +// Based on MinifyJpeg +// Source: http://www.perry.cz/files/ExifRestorer.js +// http://elicon.blog57.fc2.com/blog-entry-206.html + +var ExifRestore = function () { + function ExifRestore() { + _classCallCheck(this, ExifRestore); + } + + _createClass(ExifRestore, null, [{ + key: "initClass", + value: function initClass() { + this.KEY_STR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + } + }, { + key: "encode64", + value: function encode64(input) { + var output = ''; + var chr1 = undefined; + var chr2 = undefined; + var chr3 = ''; + var enc1 = undefined; + var enc2 = undefined; + var enc3 = undefined; + var enc4 = ''; + var i = 0; + while (true) { + chr1 = input[i++]; + chr2 = input[i++]; + chr3 = input[i++]; + enc1 = chr1 >> 2; + enc2 = (chr1 & 3) << 4 | chr2 >> 4; + enc3 = (chr2 & 15) << 2 | chr3 >> 6; + enc4 = chr3 & 63; + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + output = output + this.KEY_STR.charAt(enc1) + this.KEY_STR.charAt(enc2) + this.KEY_STR.charAt(enc3) + this.KEY_STR.charAt(enc4); + chr1 = chr2 = chr3 = ''; + enc1 = enc2 = enc3 = enc4 = ''; + if (!(i < input.length)) { + break; + } + } + return output; + } + }, { + key: "restore", + value: function restore(origFileBase64, resizedFileBase64) { + if (!origFileBase64.match('data:image/jpeg;base64,')) { + return resizedFileBase64; + } + var rawImage = this.decode64(origFileBase64.replace('data:image/jpeg;base64,', '')); + var segments = this.slice2Segments(rawImage); + var image = this.exifManipulation(resizedFileBase64, segments); + return "data:image/jpeg;base64," + this.encode64(image); + } + }, { + key: "exifManipulation", + value: function exifManipulation(resizedFileBase64, segments) { + var exifArray = this.getExifArray(segments); + var newImageArray = this.insertExif(resizedFileBase64, exifArray); + var aBuffer = new Uint8Array(newImageArray); + return aBuffer; + } + }, { + key: "getExifArray", + value: function getExifArray(segments) { + var seg = undefined; + var x = 0; + while (x < segments.length) { + seg = segments[x]; + if (seg[0] === 255 & seg[1] === 225) { + return seg; + } + x++; + } + return []; + } + }, { + key: "insertExif", + value: function insertExif(resizedFileBase64, exifArray) { + var imageData = resizedFileBase64.replace('data:image/jpeg;base64,', ''); + var buf = this.decode64(imageData); + var separatePoint = buf.indexOf(255, 3); + var mae = buf.slice(0, separatePoint); + var ato = buf.slice(separatePoint); + var array = mae; + array = array.concat(exifArray); + array = array.concat(ato); + return array; + } + }, { + key: "slice2Segments", + value: function slice2Segments(rawImageArray) { + var head = 0; + var segments = []; + while (true) { + var length; + if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 218) { + break; + } + if (rawImageArray[head] === 255 & rawImageArray[head + 1] === 216) { + head += 2; + } else { + length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3]; + var endPoint = head + length + 2; + var seg = rawImageArray.slice(head, endPoint); + segments.push(seg); + head = endPoint; + } + if (head > rawImageArray.length) { + break; + } + } + return segments; + } + }, { + key: "decode64", + value: function decode64(input) { + var output = ''; + var chr1 = undefined; + var chr2 = undefined; + var chr3 = ''; + var enc1 = undefined; + var enc2 = undefined; + var enc3 = undefined; + var enc4 = ''; + var i = 0; + var buf = []; + // remove all characters that are not A-Z, a-z, 0-9, +, /, or = + var base64test = /[^A-Za-z0-9\+\/\=]/g; + if (base64test.exec(input)) { + console.warn('There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, \'+\', \'/\',and \'=\'\nExpect errors in decoding.'); + } + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); + while (true) { + enc1 = this.KEY_STR.indexOf(input.charAt(i++)); + enc2 = this.KEY_STR.indexOf(input.charAt(i++)); + enc3 = this.KEY_STR.indexOf(input.charAt(i++)); + enc4 = this.KEY_STR.indexOf(input.charAt(i++)); + chr1 = enc1 << 2 | enc2 >> 4; + chr2 = (enc2 & 15) << 4 | enc3 >> 2; + chr3 = (enc3 & 3) << 6 | enc4; + buf.push(chr1); + if (enc3 !== 64) { + buf.push(chr2); + } + if (enc4 !== 64) { + buf.push(chr3); + } + chr1 = chr2 = chr3 = ''; + enc1 = enc2 = enc3 = enc4 = ''; + if (!(i < input.length)) { + break; + } + } + return buf; + } + }]); + + return ExifRestore; +}(); + +ExifRestore.initClass(); + +/* + * contentloaded.js + * + * Author: Diego Perini (diego.perini at gmail.com) + * Summary: cross-browser wrapper for DOMContentLoaded + * Updated: 20101020 + * License: MIT + * Version: 1.2 + * + * URL: + * http://javascript.nwbox.com/ContentLoaded/ + * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE + */ + +// @win window reference +// @fn function reference +var contentLoaded = function contentLoaded(win, fn) { + var done = false; + var top = true; + var doc = win.document; + var root = doc.documentElement; + var add = doc.addEventListener ? "addEventListener" : "attachEvent"; + var rem = doc.addEventListener ? "removeEventListener" : "detachEvent"; + var pre = doc.addEventListener ? "" : "on"; + var init = function init(e) { + if (e.type === "readystatechange" && doc.readyState !== "complete") { + return; + } + (e.type === "load" ? win : doc)[rem](pre + e.type, init, false); + if (!done && (done = true)) { + return fn.call(win, e.type || e); + } + }; + + var poll = function poll() { + try { + root.doScroll("left"); + } catch (e) { + setTimeout(poll, 50); + return; + } + return init("poll"); + }; + + if (doc.readyState !== "complete") { + if (doc.createEventObject && root.doScroll) { + try { + top = !win.frameElement; + } catch (error) { + } + if (top) { + poll(); + } + } + doc[add](pre + "DOMContentLoaded", init, false); + doc[add](pre + "readystatechange", init, false); + return win[add](pre + "load", init, false); + } +}; + +// As a single function to be able to write tests. +Dropzone._autoDiscoverFunction = function () { + if (Dropzone.autoDiscover) { + return Dropzone.discover(); + } +}; +contentLoaded(window, Dropzone._autoDiscoverFunction); + +function __guard__(value, transform) { + return typeof value !== 'undefined' && value !== null ? transform(value) : undefined; +} + +function __guardMethod__(obj, methodName, transform) { + if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') { + return transform(obj, methodName); + } else { + return undefined; + } +} diff --git a/web/client/src/js/vendor/jquery.ui.position.js b/web/client/src/js/vendor/jquery.ui.position.js new file mode 100644 index 0000000..d667e54 --- /dev/null +++ b/web/client/src/js/vendor/jquery.ui.position.js @@ -0,0 +1,495 @@ +/*! + * jQuery UI Position v1.10.0 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/position/ + */ +(function ($, undefined) { + + $.ui = $.ui || {}; + + var cachedScrollbarWidth, + max = Math.max, + abs = Math.abs, + round = Math.round, + rhorizontal = /left|center|right/, + rvertical = /top|center|bottom/, + roffset = /[\+\-]\d+%?/, + rposition = /^\w+/, + rpercent = /%$/, + _position = $.fn.position; + + function getOffsets(offsets, width, height) { + return [ + parseInt(offsets[0], 10) * (rpercent.test(offsets[0]) ? width / 100 : 1), + parseInt(offsets[1], 10) * (rpercent.test(offsets[1]) ? height / 100 : 1) + ]; + } + + function parseCss(element, property) { + return parseInt($.css(element, property), 10) || 0; + } + + function getDimensions(elem) { + var raw = elem[0]; + if (raw.nodeType === 9) { + return { + width: elem.width(), + height: elem.height(), + offset: {top: 0, left: 0} + }; + } + if ($.isWindow(raw)) { + return { + width: elem.width(), + height: elem.height(), + offset: {top: elem.scrollTop(), left: elem.scrollLeft()} + }; + } + if (raw.preventDefault) { + return { + width: 0, + height: 0, + offset: {top: raw.pageY, left: raw.pageX} + }; + } + return { + width: elem.outerWidth(), + height: elem.outerHeight(), + offset: elem.offset() + }; + } + + $.position = { + scrollbarWidth: function () { + if (cachedScrollbarWidth !== undefined) { + return cachedScrollbarWidth; + } + var w1, w2, + div = $("
"), + innerDiv = div.children()[0]; + + $("body").append(div); + w1 = innerDiv.offsetWidth; + div.css("overflow", "scroll"); + + w2 = innerDiv.offsetWidth; + + if (w1 === w2) { + w2 = div[0].clientWidth; + } + + div.remove(); + + return (cachedScrollbarWidth = w1 - w2); + }, + getScrollInfo: function (within) { + var overflowX = within.isWindow ? "" : within.element.css("overflow-x"), + overflowY = within.isWindow ? "" : within.element.css("overflow-y"), + hasOverflowX = overflowX === "scroll" || + (overflowX === "auto" && within.width < within.element[0].scrollWidth), + hasOverflowY = overflowY === "scroll" || + (overflowY === "auto" && within.height < within.element[0].scrollHeight); + return { + width: hasOverflowX ? $.position.scrollbarWidth() : 0, + height: hasOverflowY ? $.position.scrollbarWidth() : 0 + }; + }, + getWithinInfo: function (element) { + var withinElement = $(element || window), + isWindow = $.isWindow(withinElement[0]); + return { + element: withinElement, + isWindow: isWindow, + offset: withinElement.offset() || {left: 0, top: 0}, + scrollLeft: withinElement.scrollLeft(), + scrollTop: withinElement.scrollTop(), + width: isWindow ? withinElement.width() : withinElement.outerWidth(), + height: isWindow ? withinElement.height() : withinElement.outerHeight() + }; + } + }; + + $.fn.position = function (options) { + if (!options || !options.of) { + return _position.apply(this, arguments); + } + + // make a copy, we don't want to modify arguments + options = $.extend({}, options); + + var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, + target = $(options.of), + within = $.position.getWithinInfo(options.within), + scrollInfo = $.position.getScrollInfo(within), + collision = (options.collision || "flip").split(" "), + offsets = {}; + + dimensions = getDimensions(target); + if (target[0].preventDefault) { + // force left top to allow flipping + options.at = "left top"; + } + targetWidth = dimensions.width; + targetHeight = dimensions.height; + targetOffset = dimensions.offset; + // clone to reuse original targetOffset later + basePosition = $.extend({}, targetOffset); + + // force my and at to have valid horizontal and vertical positions + // if a value is missing or invalid, it will be converted to center + $.each(["my", "at"], function () { + var pos = (options[this] || "").split(" "), + horizontalOffset, + verticalOffset; + + if (pos.length === 1) { + pos = rhorizontal.test(pos[0]) ? + pos.concat(["center"]) : + rvertical.test(pos[0]) ? + ["center"].concat(pos) : + ["center", "center"]; + } + pos[0] = rhorizontal.test(pos[0]) ? pos[0] : "center"; + pos[1] = rvertical.test(pos[1]) ? pos[1] : "center"; + + // calculate offsets + horizontalOffset = roffset.exec(pos[0]); + verticalOffset = roffset.exec(pos[1]); + offsets[this] = [ + horizontalOffset ? horizontalOffset[0] : 0, + verticalOffset ? verticalOffset[0] : 0 + ]; + + // reduce to just the positions without the offsets + options[this] = [ + rposition.exec(pos[0])[0], + rposition.exec(pos[1])[0] + ]; + }); + + // normalize collision option + if (collision.length === 1) { + collision[1] = collision[0]; + } + + if (options.at[0] === "right") { + basePosition.left += targetWidth; + } else if (options.at[0] === "center") { + basePosition.left += targetWidth / 2; + } + + if (options.at[1] === "bottom") { + basePosition.top += targetHeight; + } else if (options.at[1] === "center") { + basePosition.top += targetHeight / 2; + } + + atOffset = getOffsets(offsets.at, targetWidth, targetHeight); + basePosition.left += atOffset[0]; + basePosition.top += atOffset[1]; + + return this.each(function () { + var collisionPosition, using, + elem = $(this), + elemWidth = elem.outerWidth(), + elemHeight = elem.outerHeight(), + marginLeft = parseCss(this, "marginLeft"), + marginTop = parseCss(this, "marginTop"), + collisionWidth = elemWidth + marginLeft + parseCss(this, "marginRight") + scrollInfo.width, + collisionHeight = elemHeight + marginTop + parseCss(this, "marginBottom") + scrollInfo.height, + position = $.extend({}, basePosition), + myOffset = getOffsets(offsets.my, elem.outerWidth(), elem.outerHeight()); + + if (options.my[0] === "right") { + position.left -= elemWidth; + } else if (options.my[0] === "center") { + position.left -= elemWidth / 2; + } + + if (options.my[1] === "bottom") { + position.top -= elemHeight; + } else if (options.my[1] === "center") { + position.top -= elemHeight / 2; + } + + position.left += myOffset[0]; + position.top += myOffset[1]; + + // if the browser doesn't support fractions, then round for consistent results + if (!$.support.offsetFractions) { + position.left = round(position.left); + position.top = round(position.top); + } + + collisionPosition = { + marginLeft: marginLeft, + marginTop: marginTop + }; + + $.each(["left", "top"], function (i, dir) { + if ($.ui.position[collision[i]]) { + $.ui.position[collision[i]][dir](position, { + targetWidth: targetWidth, + targetHeight: targetHeight, + elemWidth: elemWidth, + elemHeight: elemHeight, + collisionPosition: collisionPosition, + collisionWidth: collisionWidth, + collisionHeight: collisionHeight, + offset: [atOffset[0] + myOffset[0], atOffset [1] + myOffset[1]], + my: options.my, + at: options.at, + within: within, + elem: elem + }); + } + }); + + if (options.using) { + // adds feedback as second argument to using callback, if present + using = function (props) { + var left = targetOffset.left - position.left, + right = left + targetWidth - elemWidth, + top = targetOffset.top - position.top, + bottom = top + targetHeight - elemHeight, + feedback = { + target: { + element: target, + left: targetOffset.left, + top: targetOffset.top, + width: targetWidth, + height: targetHeight + }, + element: { + element: elem, + left: position.left, + top: position.top, + width: elemWidth, + height: elemHeight + }, + horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", + vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" + }; + if (targetWidth < elemWidth && abs(left + right) < targetWidth) { + feedback.horizontal = "center"; + } + if (targetHeight < elemHeight && abs(top + bottom) < targetHeight) { + feedback.vertical = "middle"; + } + if (max(abs(left), abs(right)) > max(abs(top), abs(bottom))) { + feedback.important = "horizontal"; + } else { + feedback.important = "vertical"; + } + options.using.call(this, props, feedback); + }; + } + + elem.offset($.extend(position, {using: using})); + }); + }; + + $.ui.position = { + fit: { + left: function (position, data) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, + outerWidth = within.width, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = withinOffset - collisionPosLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, + newOverRight; + + // element is wider than within + if (data.collisionWidth > outerWidth) { + // element is initially over the left side of within + if (overLeft > 0 && overRight <= 0) { + newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset; + position.left += overLeft - newOverRight; + // element is initially over right side of within + } else if (overRight > 0 && overLeft <= 0) { + position.left = withinOffset; + // element is initially over both left and right sides of within + } else { + if (overLeft > overRight) { + position.left = withinOffset + outerWidth - data.collisionWidth; + } else { + position.left = withinOffset; + } + } + // too far left -> align with left edge + } else if (overLeft > 0) { + position.left += overLeft; + // too far right -> align with right edge + } else if (overRight > 0) { + position.left -= overRight; + // adjust based on position and margin + } else { + position.left = max(position.left - collisionPosLeft, position.left); + } + }, + top: function (position, data) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollTop : within.offset.top, + outerHeight = data.within.height, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = withinOffset - collisionPosTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, + newOverBottom; + + // element is taller than within + if (data.collisionHeight > outerHeight) { + // element is initially over the top of within + if (overTop > 0 && overBottom <= 0) { + newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset; + position.top += overTop - newOverBottom; + // element is initially over bottom of within + } else if (overBottom > 0 && overTop <= 0) { + position.top = withinOffset; + // element is initially over both top and bottom of within + } else { + if (overTop > overBottom) { + position.top = withinOffset + outerHeight - data.collisionHeight; + } else { + position.top = withinOffset; + } + } + // too far up -> align with top + } else if (overTop > 0) { + position.top += overTop; + // too far down -> align with bottom edge + } else if (overBottom > 0) { + position.top -= overBottom; + // adjust based on position and margin + } else { + position.top = max(position.top - collisionPosTop, position.top); + } + } + }, + flip: { + left: function (position, data) { + var within = data.within, + withinOffset = within.offset.left + within.scrollLeft, + outerWidth = within.width, + offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = collisionPosLeft - offsetLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, + myOffset = data.my[0] === "left" ? + -data.elemWidth : + data.my[0] === "right" ? + data.elemWidth : + 0, + atOffset = data.at[0] === "left" ? + data.targetWidth : + data.at[0] === "right" ? + -data.targetWidth : + 0, + offset = -2 * data.offset[0], + newOverRight, + newOverLeft; + + if (overLeft < 0) { + newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset; + if (newOverRight < 0 || newOverRight < abs(overLeft)) { + position.left += myOffset + atOffset + offset; + } + } else if (overRight > 0) { + newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft; + if (newOverLeft > 0 || abs(newOverLeft) < overRight) { + position.left += myOffset + atOffset + offset; + } + } + }, + top: function (position, data) { + var within = data.within, + withinOffset = within.offset.top + within.scrollTop, + outerHeight = within.height, + offsetTop = within.isWindow ? within.scrollTop : within.offset.top, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = collisionPosTop - offsetTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, + top = data.my[1] === "top", + myOffset = top ? + -data.elemHeight : + data.my[1] === "bottom" ? + data.elemHeight : + 0, + atOffset = data.at[1] === "top" ? + data.targetHeight : + data.at[1] === "bottom" ? + -data.targetHeight : + 0, + offset = -2 * data.offset[1], + newOverTop, + newOverBottom; + if (overTop < 0) { + newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset; + if ((position.top + myOffset + atOffset + offset) > overTop && (newOverBottom < 0 || newOverBottom < abs(overTop))) { + position.top += myOffset + atOffset + offset; + } + } else if (overBottom > 0) { + newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop; + if ((position.top + myOffset + atOffset + offset) > overBottom && (newOverTop > 0 || abs(newOverTop) < overBottom)) { + position.top += myOffset + atOffset + offset; + } + } + } + }, + flipfit: { + left: function () { + $.ui.position.flip.left.apply(this, arguments); + $.ui.position.fit.left.apply(this, arguments); + }, + top: function () { + $.ui.position.flip.top.apply(this, arguments); + $.ui.position.fit.top.apply(this, arguments); + } + } + }; + +// fraction support test + (function () { + var testElement, testElementParent, testElementStyle, offsetLeft, i, + body = document.getElementsByTagName("body")[0], + div = document.createElement("div"); + + //Create a "fake body" for testing based on method used in jQuery.support + testElement = document.createElement(body ? "div" : "body"); + testElementStyle = { + visibility: "hidden", + width: 0, + height: 0, + border: 0, + margin: 0, + background: "none" + }; + if (body) { + $.extend(testElementStyle, { + position: "absolute", + left: "-1000px", + top: "-1000px" + }); + } + for (i in testElementStyle) { + testElement.style[i] = testElementStyle[i]; + } + testElement.appendChild(div); + testElementParent = body || document.documentElement; + testElementParent.insertBefore(testElement, testElementParent.firstChild); + + div.style.cssText = "position: absolute; left: 10.7432222px;"; + + offsetLeft = $(div).offset().left; + $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11; + + testElement.innerHTML = ""; + testElementParent.removeChild(testElement); + })(); + +}(jQuery)); diff --git a/web/client/src/js/vendor/modernizr-2.8.3-respond-1.4.2.min.js b/web/client/src/js/vendor/modernizr-2.8.3-respond-1.4.2.min.js new file mode 100644 index 0000000..69fb72a --- /dev/null +++ b/web/client/src/js/vendor/modernizr-2.8.3-respond-1.4.2.min.js @@ -0,0 +1,11 @@ +/* Modernizr 2.8.3 (Custom Build) | MIT & BSD + * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load + */ +;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a){var e=a[d];if(!G(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function K(){e.input=function(c){for(var d=0,e=c.length;d',a,""].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b)&&c(b).matches||!1;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function l(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function m(){var a=s.elements;return typeof a=="string"?a.split(" "):a}function n(a){var b=j[a[h]];return b||(b={},i++,a[h]=i,j[i]=b),b}function o(a,c,d){c||(c=b);if(k)return c.createElement(a);d||(d=n(c));var g;return d.cache[a]?g=d.cache[a].cloneNode():f.test(a)?g=(d.cache[a]=d.createElem(a)).cloneNode():g=d.createElem(a),g.canHaveChildren&&!e.test(a)&&!g.tagUrn?d.frag.appendChild(g):g}function p(a,c){a||(a=b);if(k)return a.createDocumentFragment();c=c||n(a);var d=c.frag.cloneNode(),e=0,f=m(),g=f.length;for(;e",g="hidden"in a,k=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){g=!0,k=!0}})();var s={elements:d.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:c,shivCSS:d.shivCSS!==!1,supportsUnknownElements:k,shivMethods:d.shivMethods!==!1,type:"default",shivDocument:r,createElement:o,createDocumentFragment:p};a.html5=s,r(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f #mq-test-1 { width: 42px; }',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){v(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},g=function(a){return a.replace(c.regex.minmaxwh,"").match(c.regex.other)};if(c.ajax=f,c.queue=d,c.unsupportedmq=g,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,comments:/\/\*[^*]*\*+([^/][^*]*\*+)*\//gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,maxw:/\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,minmaxwh:/\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi,other:/\([^\)]*\)/g},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var h,i,j,k=a.document,l=k.documentElement,m=[],n=[],o=[],p={},q=30,r=k.getElementsByTagName("head")[0]||l,s=k.getElementsByTagName("base")[0],t=r.getElementsByTagName("link"),u=function(){var a,b=k.createElement("div"),c=k.body,d=l.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=k.createElement("body"),c.style.background="none"),l.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&l.insertBefore(c,l.firstChild),a=b.offsetWidth,f?l.removeChild(c):c.removeChild(b),l.style.fontSize=d,e&&(c.style.fontSize=e),a=j=parseFloat(a)},v=function(b){var c="clientWidth",d=l[c],e="CSS1Compat"===k.compatMode&&d||k.body[c]||d,f={},g=t[t.length-1],p=(new Date).getTime();if(b&&h&&q>p-h)return a.clearTimeout(i),i=a.setTimeout(v,q),void 0;h=p;for(var s in m)if(m.hasOwnProperty(s)){var w=m[s],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?j||u():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?j||u():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(n[w.rules]))}for(var C in o)o.hasOwnProperty(C)&&o[C]&&o[C].parentNode===r&&r.removeChild(o[C]);o.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=k.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,r.insertBefore(E,g.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(k.createTextNode(F)),o.push(E)}},w=function(a,b,d){var e=a.replace(c.regex.comments,"").replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},i=!f&&d;b.length&&(b+="/"),i&&(f=1);for(var j=0;f>j;j++){var k,l,o,p;i?(k=d,n.push(h(a))):(k=e[j].match(c.regex.findStyles)&&RegExp.$1,n.push(RegExp.$2&&h(RegExp.$2))),o=k.split(","),p=o.length;for(var q=0;p>q;q++)l=o[q],g(l)||m.push({media:l.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:n.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}v()},x=function(){if(d.length){var b=d.shift();f(b.href,function(c){w(c,b.href,b.media),p[b.href]=!0,a.setTimeout(function(){x()},0)})}},y=function(){for(var b=0;b console.log(err)); diff --git a/web/client/src/polyfills.ts b/web/client/src/polyfills.ts new file mode 100644 index 0000000..1f9172a --- /dev/null +++ b/web/client/src/polyfills.ts @@ -0,0 +1,69 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +// import 'core-js/es6/symbol'; +// import 'core-js/es6/object'; +// import 'core-js/es6/function'; +// import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +// import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +// import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; +// import 'core-js/es6/map'; +// import 'core-js/es6/weak-map'; +// import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** Evergreen browsers require these. **/ +import 'core-js/es/reflect'; + + +/** + * Required to support Web Animations `@angular/animation`. + * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation + **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js'; // Included with Angular CLI. + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. +/** + * Need to import at least one locale-data with intl. + */ +// import 'intl/locale-data/jsonp/en'; diff --git a/web/client/src/styles.css b/web/client/src/styles.css new file mode 100644 index 0000000..f2638b3 --- /dev/null +++ b/web/client/src/styles.css @@ -0,0 +1,520 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +@import '@angular/material/prebuilt-themes/indigo-pink.css'; +@import "material-symbols/rounded.css"; + +html, +body { + background-color: #fafafa; + width: 100%; + height: 100%; + margin: 0; + border: 0; + display: block; +} + +a { + cursor: pointer; +} + +.dragging { + cursor: grabbing !important; +} + +.draggable { + cursor: grab !important; +} + +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 5px; + margin-bottom: 5px; +} + +.help-block { + font-size: 12px; +} + +.navbar { + margin-bottom: 5px; +} + +#requestedNameFormField .mat-mdc-form-field-required-marker { + color: red !important +} + +.btn-sm { + padding: .25rem .5rem !important; + font-size: .875rem !important; + line-height: 1.5 !important; + border-radius: .2rem !important; + min-width: unset !important; +} + +.btn-sm .mat-icon, .btn-sm mat-icon, .btn-sm .material-symbols-rounded { + font-size: 20px; +} + +.btn-xs .mat-icon, .btn-xs mat-icon { + font-size: 13px; +} + +.btn-sm .mat-icon, .btn-sm mat-icon, +.btn-xs .mat-icon, .btn-xs mat-icon, +.btn-sm .material-symbols-rounded, +.btn-xs .material-symbols-rounded { + vertical-align: middle; + height: unset; + width: unset; +} + +#content { + background: white; + -webkit-transform-origin: left top; + -webkit-transform: translateZ(0); + -moz-transform-origin: left top; + -moz-transform: translateZ(0); + -ms-transform-origin: left top; + -ms-transform: translateZ(0); + -o-transform-origin: left top; + -o-transform: translateZ(0); + transform-origin: left top; + transform: translateZ(0); +} + +#settings { + border: 2px solid black; +} + +#canvas_wrap { + overflow: auto; +} + +#canvas-wrapper .mat-mdc-tab-labels { + border-top: 1px solid #dfdedf; + border-right: 1px solid #dfdedf; +} + +.page-actions { + position: fixed; + bottom: 20px; + right: 20px; +} + +.mat-mdc-card.card-tab-groups { + padding: 0; +} + +.mat-mdc-card.card-tab-groups .mat-mdc-tab-group { + width: 100%; +} + +.card-tab-groups .mat-mdc-tab-body-content { + padding: 20px; +} + +.mat-mdc-tooltip.large-text { + font-size: 15px; +} + +.status-menu .mat-mdc-menu-content { + padding: 0 !important; +} + +table#service-status-table tr:last-child td { + border-bottom: none; +} + +/* Override for button focus artifact that comes from having both bootstrap & material enabled */ +.mat-mdc-button:focus, .mat-mdc-unelevated-button:focus, +.mat-mdc-icon-button:focus, .mat-mdc-outlined-button:focus, .mat-mdc-menu-item:focus, +.mat-mdc-fab:focus, .mat-mdc-mini-fab:focus { + outline: none !important; +} + +/* Override for material labels not liking the bottom margin that bootstrap labels typically get*/ +label[class^="mat-"], label[class*=" mat-"] { + margin-bottom: unset; +} + +.center-div { + width: 70px; + float: left; + display: inline-block; + text-align: center; +} + +.markup-canvas { + position: relative; + width: 100%; + height: 400px; + border: 2px solid grey; +} + +.markup-canvas .inner-markup { + text-align: center; + padding-top: 35px; + /*margin: 0 auto;*/ + /*width: 60%;*/ +} + +.rect-canvas { + width: 100%; + height: 100px; + margin-top: 5px; +} + +.rect-div { + width: 100%; + height: 100px; + border: 2px solid grey; +} + +.activeCanvas { + border: 4px solid red; +} + +.rectangle-canvas { + border: 2px solid grey; +} + +#reviewers-list { + list-style-type: none; +} + +#reviewers-list li { + text-decoration: underline; +} + +.list-group-item { + padding: 3px 6px; +} + +.color_slider_val { + padding: 0 5px; +} + +/* TOOLS */ +.tools { + display: none; +} + +.iris-tool { + padding: 3px; +} + +#tool_pallet_extra { + padding-left: 10px; + width: 50%; + display: inline; + min-height: 20px; +} + +.tool-item { + display: inline; +} + +.help { + padding: 5px 0; + text-align: center; +} + +.poly-list-group { + list-style-type: none; +} + +.poly-choice-btn { + margin: 3px; + color: #000; + text-decoration: underline; +} + +.poly-text-input { + color: #000; + display: inline; + width: 60%; +} + +.polytool { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.pencil-list-group { + list-style-type: none; + box-shadow: none; +} + +.simplecolorpicker.icon, +.simplecolorpicker span.color { + margin-left: 5px; + font-size: 10px; + display: inline-block; + cursor: pointer; + border: 1px solid black; +} + +.progress { + position: relative; + margin-bottom: 2px; +} + +.progress-text { + position: absolute; + display: block; + width: 100%; + color: black; +} + +.modal-title { + font-weight: bold; +} + + +.nav-text { + vertical-align: bottom; +} + +.item-img { + display: inline-block; + width: 22%; + vertical-align: top; +} + +.item-img img { + width: 20px; + height: 20px; +} + +.item-title { + display: block; + font-weight: bold; +} + +.item-body { + display: inline-block; + width: 75%; +} + +.item-tools { + float: right; +} + +.item-required { + margin-left: -10px; +} + +.item-complete { + padding-left: 3px; + display: inline; +} + +.fa-info-circle, +.fa-eye, +.fa-eye-slash, +.fa-plus, +.fa-trash, +.fa-undo { + cursor: pointer; +} + +.inline-tool { + display: inline; +} + +.inline-headers h2, .inline-headers h4 { + display: inline-block; + vertical-align: baseline; +} + +input[type="range"].slider { + opacity: 1; + -webkit-appearance: slider-horizontal; + flex: 1; + height: 20px; + margin: 0 5px; +} + +.color-picker { + width: 30px; +} + +.color-level-tool { + float: left; + padding-left: 5px; +} + +.color-level-tool .slider { + max-width: 300px; + +} + +.annotation-info color-picker .arrow { + display: none !important; +} + +.annotation-list { + overflow: auto; + max-height: 80px; +} + +#previewModal .modal-body { + height: 600px; + overflow: auto; +} + +.thumb { + height: 130px; +} + +.comments { + max-height: 500px; + overflow: auto; +} + +.comment { + padding: 10px 30px; +} + +#caseInfo .tab-pane { + padding-top: 10px; +} + +.tab-content { + border: 1px solid #dee2e6; + border-top: none; + padding: .5rem; +} + +.caseReqImage { + overflow: auto; + width: 100%; +} + +/* +TODO(mdc-migration): The following rule targets internal classes of tabs that may no longer apply for the MDC version. +*/ +.mat-tab-body-wrapper { + flex-grow: 1; +} + +body { + margin: 0; + font-family: Roboto, "Helvetica Neue", sans-serif; +} + +.pointer { + cursor: pointer; +} + +.hover-color:hover { + background-color: #f0f0f0; +} + +.toolbar-spacer { + flex: 1 1 auto; +} + +.canvas-dropzone .dropzone, .canvas-dropzone .dz-message { + height: 380px !important; + overflow: visible !important; +} + +/* Initialization for locally hosted google material icons */ + +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url(https://example.com/MaterialIcons-Regular.eot); /* For IE6-8 */ + src: local('Material Icons'), + local('MaterialIcons-Regular'), + url(fonts/MaterialIcons-Regular.woff2) format('woff2'), + url(fonts/MaterialIcons-Regular.woff) format('woff'), + url(fonts/MaterialIcons-Regular.ttf) format('truetype'); +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; /* Preferred icon size */ + display: inline-block; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + + /* Support for all WebKit browsers. */ + -webkit-font-smoothing: antialiased; + /* Support for Safari and Chrome. */ + text-rendering: optimizeLegibility; + + /* Support for Firefox. */ + -moz-osx-font-smoothing: grayscale; + + /* Support for IE. */ + font-feature-settings: 'liga'; +} + +.cdk-global-overlay-wrapper, .cdk-overlay-container { + z-index: 9999 !important; +} + +.cdk-overlay-connected-position-bounding-box { + z-index: 100000 !important; +} + +button mat-spinner, button mat-spinner svg { + height: 100% !important; + width: 100% !important; +} + +.mr-sm { + margin-right: 2px !important; +} + +.ml-sm { + margin-left: 2px !important; +} + +.notification-item .mat-list-item-content { + padding-right: 5px !important; +} + +.notification-dropdown .mat-mdc-menu-content { + padding-bottom: 0 !important; +} + +.mat-mdc-menu-panel.notification-dropdown { + min-width: 280px; +} + +.notification-dropdown .mat-mdc-list-base { + padding-top: 0px; + padding-bottom: 8px; +} + +#tab-group mat-tab-body { + height: 497px; + border-right: 1px #dfdedf solid; + border-bottom: 1px #dfdedf solid; +} + +.mdc-tab__content { + font-weight: 900; +} \ No newline at end of file diff --git a/web/client/src/test.ts b/web/client/src/test.ts new file mode 100644 index 0000000..87b5352 --- /dev/null +++ b/web/client/src/test.ts @@ -0,0 +1,23 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/testing'; +import {getTestBed} from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); diff --git a/web/client/src/theme.scss b/web/client/src/theme.scss new file mode 100644 index 0000000..99af7c1 --- /dev/null +++ b/web/client/src/theme.scss @@ -0,0 +1,43 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +@use '@angular/material' as mat; +@use "sass:map"; + +@include mat.core(); +@include mat.legacy-typography-hierarchy(mat.define-typography-config()); + +$app-primary: mat.define-palette(mat.$indigo-palette); +$app-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400); + +$app-theme: mat.define-light-theme($app-primary, $app-accent); + +@mixin mix-app-theme($app-theme) { + $primary: map-get($app-theme, primary); + $accent: map-get($app-theme, accent); + .mat-button-toggle-checked { + background-color: mat.get-color-from-palette($primary); + color: white !important; + } +} + +.btn-xs-wrap { + button { + padding: 0.15rem 0.3rem !important; + font-size: 0.675rem !important; + line-height: 1.3 !important; + border-radius: .2rem !important; + min-width: unset !important; + } + @include mat.button-density(-3) +} + +// Include the mixin +@include mix-app-theme($app-theme); diff --git a/web/client/src/tsconfig.app.json b/web/client/src/tsconfig.app.json new file mode 100644 index 0000000..2e7a5b2 --- /dev/null +++ b/web/client/src/tsconfig.app.json @@ -0,0 +1,15 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "baseUrl": "./", + "types": [] + }, + "files": [ + "main.ts", + "polyfills.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/web/client/src/tsconfig.spec.json b/web/client/src/tsconfig.spec.json new file mode 100644 index 0000000..c89454b --- /dev/null +++ b/web/client/src/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "baseUrl": "./", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts", + "polyfills.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/web/client/src/zone-testing.d.ts b/web/client/src/zone-testing.d.ts new file mode 100644 index 0000000..ec9047f --- /dev/null +++ b/web/client/src/zone-testing.d.ts @@ -0,0 +1,13 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) - Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +// zone.js exposes this side-effect test bundle, but does not ship declarations +// for the subpath in the installed 0.14.x package. +declare module 'zone.js/testing'; diff --git a/web/client/tsconfig.json b/web/client/tsconfig.json new file mode 100644 index 0000000..8cfe9ab --- /dev/null +++ b/web/client/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "importHelpers": true, + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "ES2022", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2017", + "dom" + ], + "downlevelIteration": true, + "module": "es2020", + "baseUrl": "./", + "useDefineForClassFields": false + }, + "exclude": [ + "node_modules/*" + ], + "angularCompilerOptions": { + "strictTemplates": true + } +} diff --git a/web/client/yarn.lock b/web/client/yarn.lock new file mode 100644 index 0000000..3d050e3 --- /dev/null +++ b/web/client/yarn.lock @@ -0,0 +1,9380 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + +"@ampproject/remapping@2.3.0", "@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@angular-devkit/architect@0.1703.1": + version "0.1703.1" + resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1703.1.tgz" + integrity sha512-vkfvURv7O+3fHMTE9K+yUEiFS0v4JNYKsDP0LE1ChH5Ocy0bJXGcH2Cyz2W8qdJGDG/tKe41VzvOLpu88Xv3zQ== + dependencies: + "@angular-devkit/core" "17.3.1" + rxjs "7.8.1" + +"@angular-devkit/build-angular@17.3.1": + version "17.3.1" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-17.3.1.tgz" + integrity sha512-e+hZvLVH5AvHCFbVtKRd5oJeFsEmjg7kK1V6hsVxH4YE2f2x399TSr+AGxwV+R3jnjZ67ujIeXXd0Uuf1RwcSg== + dependencies: + "@ampproject/remapping" "2.3.0" + "@angular-devkit/architect" "0.1703.1" + "@angular-devkit/build-webpack" "0.1703.1" + "@angular-devkit/core" "17.3.1" + "@babel/core" "7.24.0" + "@babel/generator" "7.23.6" + "@babel/helper-annotate-as-pure" "7.22.5" + "@babel/helper-split-export-declaration" "7.22.6" + "@babel/plugin-transform-async-generator-functions" "7.23.9" + "@babel/plugin-transform-async-to-generator" "7.23.3" + "@babel/plugin-transform-runtime" "7.24.0" + "@babel/preset-env" "7.24.0" + "@babel/runtime" "7.24.0" + "@discoveryjs/json-ext" "0.5.7" + "@ngtools/webpack" "17.3.1" + "@vitejs/plugin-basic-ssl" "1.1.0" + ansi-colors "4.1.3" + autoprefixer "10.4.18" + babel-loader "9.1.3" + babel-plugin-istanbul "6.1.1" + browserslist "^4.21.5" + copy-webpack-plugin "11.0.0" + critters "0.0.22" + css-loader "6.10.0" + esbuild-wasm "0.20.1" + fast-glob "3.3.2" + http-proxy-middleware "2.0.6" + https-proxy-agent "7.0.4" + inquirer "9.2.15" + jsonc-parser "3.2.1" + karma-source-map-support "1.4.0" + less "4.2.0" + less-loader "11.1.0" + license-webpack-plugin "4.0.2" + loader-utils "3.2.1" + magic-string "0.30.8" + mini-css-extract-plugin "2.8.1" + mrmime "2.0.0" + open "8.4.2" + ora "5.4.1" + parse5-html-rewriting-stream "7.0.0" + picomatch "4.0.1" + piscina "4.4.0" + postcss "8.4.35" + postcss-loader "8.1.1" + resolve-url-loader "5.0.0" + rxjs "7.8.1" + sass "1.71.1" + sass-loader "14.1.1" + semver "7.6.0" + source-map-loader "5.0.0" + source-map-support "0.5.21" + terser "5.29.1" + tree-kill "1.2.2" + tslib "2.6.2" + undici "6.7.1" + vite "5.1.5" + watchpack "2.4.0" + webpack "5.90.3" + webpack-dev-middleware "6.1.1" + webpack-dev-server "4.15.1" + webpack-merge "5.10.0" + webpack-subresource-integrity "5.1.0" + optionalDependencies: + esbuild "0.20.1" + +"@angular-devkit/build-webpack@0.1703.1": + version "0.1703.1" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1703.1.tgz" + integrity sha512-nVUzewX8RCzaEPQZ1JQpE42wpsYchKQwfXUSCkoUsuCMB2c6zuEz0Jt94nzJg3UjSEEV4ZqCH8v5MDOvB49Rlw== + dependencies: + "@angular-devkit/architect" "0.1703.1" + rxjs "7.8.1" + +"@angular-devkit/core@17.3.1": + version "17.3.1" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-17.3.1.tgz" + integrity sha512-EP7zwqBEaOPuBJwzKmh2abfgNFITGX178BOyTG6zTymeMzEbrvy2OdeQXSslkJ/RGLCpx60GT+0CFW7wGlQR6Q== + dependencies: + ajv "8.12.0" + ajv-formats "2.1.1" + jsonc-parser "3.2.1" + picomatch "4.0.1" + rxjs "7.8.1" + source-map "0.7.4" + +"@angular-devkit/schematics@17.3.1": + version "17.3.1" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-17.3.1.tgz" + integrity sha512-c3tp5zC5zp6XpK9w8wJf3d4Dyw9BNbmg/VEoXtePGivp4hzks6zuMAFknNRwdK7roOlH0HyM5No4WUZHBFpOmw== + dependencies: + "@angular-devkit/core" "17.3.1" + jsonc-parser "3.2.1" + magic-string "0.30.8" + ora "5.4.1" + rxjs "7.8.1" + +"@angular-eslint/builder@17.3.0": + version "17.3.0" + resolved "https://registry.yarnpkg.com/@angular-eslint/builder/-/builder-17.3.0.tgz" + integrity sha512-JXSZE7+KA3UGU6jwc0v9lwOIMptosrvLIOXGlXqrhHWEXfkfu3ENPq1Lm3K8jLndQ57XueEhC+Nab/AuUiWA/Q== + dependencies: + "@nx/devkit" "^17.2.8 || ^18.0.0" + nx "^17.2.8 || ^18.0.0" + +"@angular-eslint/bundled-angular-compiler@17.3.0": + version "17.3.0" + resolved "https://registry.yarnpkg.com/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-17.3.0.tgz" + integrity sha512-ejfNzRuBeHUV8m2fkgs+M809rj5STuCuQo4fdfc6ccQpzXDI6Ha7BKpTznWfg5g529q/wrkoGSGgFxU9Yc2/dQ== + +"@angular-eslint/eslint-plugin-template@17.3.0": + version "17.3.0" + resolved "https://registry.yarnpkg.com/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-17.3.0.tgz" + integrity sha512-9l/aRfpE9MCRVDWRb+rSB9Zei0paep1vqV6M/87VUnzBnzqeMRnVuPvQowilh2zweVSGKBF25Vp4HkwOL6ExDQ== + dependencies: + "@angular-eslint/bundled-angular-compiler" "17.3.0" + "@angular-eslint/utils" "17.3.0" + "@typescript-eslint/type-utils" "7.2.0" + "@typescript-eslint/utils" "7.2.0" + aria-query "5.3.0" + axobject-query "4.0.0" + +"@angular-eslint/eslint-plugin@17.3.0": + version "17.3.0" + resolved "https://registry.yarnpkg.com/@angular-eslint/eslint-plugin/-/eslint-plugin-17.3.0.tgz" + integrity sha512-81cQbOEPoQupFX8WmpqZn+y8VA7JdVRGBtt+uJNKBXcJknTpPWdLBZRFlgVakmC24iEZ0Fint/N3NBBQI3mz2A== + dependencies: + "@angular-eslint/utils" "17.3.0" + "@typescript-eslint/utils" "7.2.0" + +"@angular-eslint/schematics@17.3.0": + version "17.3.0" + resolved "https://registry.yarnpkg.com/@angular-eslint/schematics/-/schematics-17.3.0.tgz" + integrity sha512-5yssd5EOomxlKt9vN/OXXCTCuI3Pmfj16pkjBDoW0wzC8/M2l5zlXIEfoKumHYv2wtF553LhaMXVYVU35e0lTw== + dependencies: + "@angular-eslint/eslint-plugin" "17.3.0" + "@angular-eslint/eslint-plugin-template" "17.3.0" + "@nx/devkit" "^17.2.8 || ^18.0.0" + ignore "5.3.1" + nx "^17.2.8 || ^18.0.0" + strip-json-comments "3.1.1" + tmp "0.2.3" + +"@angular-eslint/template-parser@17.3.0": + version "17.3.0" + resolved "https://registry.yarnpkg.com/@angular-eslint/template-parser/-/template-parser-17.3.0.tgz" + integrity sha512-m+UzAnWgtjeS0x6skSmR0eXltD/p7HZA+c8pPyAkiHQzkxE7ohhfyZc03yWGuYJvWQUqQAKKdO/nQop14TP0bg== + dependencies: + "@angular-eslint/bundled-angular-compiler" "17.3.0" + eslint-scope "^8.0.0" + +"@angular-eslint/utils@17.3.0": + version "17.3.0" + resolved "https://registry.yarnpkg.com/@angular-eslint/utils/-/utils-17.3.0.tgz" + integrity sha512-PJT9pxWqpvI9OXO+7L5SIVhvMW+RFjeafC7PYjtvSbNFpz+kF644BiAcfMJ0YqBnkrw3JXt+RAX25CT4mXIoXw== + dependencies: + "@angular-eslint/bundled-angular-compiler" "17.3.0" + "@typescript-eslint/utils" "7.2.0" + +"@angular/animations@17.3.1": + version "17.3.1" + resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-17.3.1.tgz" + integrity sha512-2TZ0M5J0IizhHpb404DeqArlv8Ki9BFz5ZUuET2uFROpKW8IMDCht8fSrn/DKHpjB9lvzPUhNFaRxNWEY6klnA== + dependencies: + tslib "^2.3.0" + +"@angular/cdk@^16.2.14": + version "16.2.14" + resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-16.2.14.tgz" + integrity sha512-n6PrGdiVeSTEmM/HEiwIyg6YQUUymZrb5afaNLGFRM5YL0Y8OBqd+XhCjb0OfD/AfgCUtedVEPwNqrfW8KzgGw== + dependencies: + tslib "^2.3.0" + optionalDependencies: + parse5 "^7.1.2" + +"@angular/cli@17.3.1": + version "17.3.1" + resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-17.3.1.tgz" + integrity sha512-IVnnbRi53BZvZ3LE0PCfFefoB2uHlO1sHtilZf/xCpdV4E1Mkz0/hHln5CRHwAXErdSiY57VoMsF5tffxAfaBQ== + dependencies: + "@angular-devkit/architect" "0.1703.1" + "@angular-devkit/core" "17.3.1" + "@angular-devkit/schematics" "17.3.1" + "@schematics/angular" "17.3.1" + "@yarnpkg/lockfile" "1.1.0" + ansi-colors "4.1.3" + ini "4.1.2" + inquirer "9.2.15" + jsonc-parser "3.2.1" + npm-package-arg "11.0.1" + npm-pick-manifest "9.0.0" + open "8.4.2" + ora "5.4.1" + pacote "17.0.6" + resolve "1.22.8" + semver "7.6.0" + symbol-observable "4.0.0" + yargs "17.7.2" + +"@angular/common@17.3.1": + version "17.3.1" + resolved "https://registry.yarnpkg.com/@angular/common/-/common-17.3.1.tgz" + integrity sha512-HyUTJ4RxhE3bOmFRV6Fv2y01ixbrUb8Hd4MxPm8REbNMGKsWCfXhR3FfxFL18Sc03SAF+o0Md0wwekjFKTNKfQ== + dependencies: + tslib "^2.3.0" + +"@angular/compiler-cli@17.3.0": + version "17.3.0" + resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-17.3.0.tgz" + integrity sha512-ewo+pb0QUC69Ey15z4vPteoBeO81HitqplysOoeXbyVBjMnKmZl3343wx7ukgcI97lmj4d38d1r4AnIoO5n/Vw== + dependencies: + "@babel/core" "7.23.9" + "@jridgewell/sourcemap-codec" "^1.4.14" + chokidar "^3.0.0" + convert-source-map "^1.5.1" + reflect-metadata "^0.2.0" + semver "^7.0.0" + tslib "^2.3.0" + yargs "^17.2.1" + +"@angular/compiler@17.3.1": + version "17.3.1" + resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-17.3.1.tgz" + integrity sha512-8qqlWPGZEyD2FY5losOW3Aocro+lFysPDzsf0LHgQUM6Ub1b+pq4jUOjH6w0vzaxG3TfxkgzOQ9aNdWtSV67Rg== + dependencies: + tslib "^2.3.0" + +"@angular/core@17.3.1": + version "17.3.1" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-17.3.1.tgz" + integrity sha512-Qf3/sgkXS1LHwOTtqAVYprySrn0YpPIZqerPc0tK+hyQfwAz5BQlpcBhbH8RWKlfCY8eO0cqo/j0+e8DQOgYfg== + dependencies: + tslib "^2.3.0" + +"@angular/forms@17.3.1": + version "17.3.1" + resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-17.3.1.tgz" + integrity sha512-HndsO90k67sFHzd+sII+rhAUksffBvquFuAUCc6QR9WVjILxVg2fY7oBidgS1gKNqu0mptPG0GvuORnaW/0gSg== + dependencies: + tslib "^2.3.0" + +"@angular/language-service@17.3.0": + version "17.3.0" + resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-17.3.0.tgz" + integrity sha512-h4bwuyeAmZkoeCM/KvhWW+p2xjKiVt4GfSWZsIuW5ilfJt7hmkUFGyu0ABCjt6fiNQRrS2tvBZdXxk+A+zX8KQ== + +"@angular/material@^16.2.14": + version "16.2.14" + resolved "https://registry.yarnpkg.com/@angular/material/-/material-16.2.14.tgz" + integrity sha512-zQIxUb23elPfiIvddqkIDYqQhAHa9ZwMblfbv+ug8bxr4D0Dw360jIarxCgMjAcLj7Ccl3GBqZMUnVeM6cjthw== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/auto-init" "15.0.0-canary.bc9ae6c9c.0" + "@material/banner" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/button" "15.0.0-canary.bc9ae6c9c.0" + "@material/card" "15.0.0-canary.bc9ae6c9c.0" + "@material/checkbox" "15.0.0-canary.bc9ae6c9c.0" + "@material/chips" "15.0.0-canary.bc9ae6c9c.0" + "@material/circular-progress" "15.0.0-canary.bc9ae6c9c.0" + "@material/data-table" "15.0.0-canary.bc9ae6c9c.0" + "@material/density" "15.0.0-canary.bc9ae6c9c.0" + "@material/dialog" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/drawer" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/fab" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/floating-label" "15.0.0-canary.bc9ae6c9c.0" + "@material/form-field" "15.0.0-canary.bc9ae6c9c.0" + "@material/icon-button" "15.0.0-canary.bc9ae6c9c.0" + "@material/image-list" "15.0.0-canary.bc9ae6c9c.0" + "@material/layout-grid" "15.0.0-canary.bc9ae6c9c.0" + "@material/line-ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/linear-progress" "15.0.0-canary.bc9ae6c9c.0" + "@material/list" "15.0.0-canary.bc9ae6c9c.0" + "@material/menu" "15.0.0-canary.bc9ae6c9c.0" + "@material/menu-surface" "15.0.0-canary.bc9ae6c9c.0" + "@material/notched-outline" "15.0.0-canary.bc9ae6c9c.0" + "@material/radio" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/segmented-button" "15.0.0-canary.bc9ae6c9c.0" + "@material/select" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/slider" "15.0.0-canary.bc9ae6c9c.0" + "@material/snackbar" "15.0.0-canary.bc9ae6c9c.0" + "@material/switch" "15.0.0-canary.bc9ae6c9c.0" + "@material/tab" "15.0.0-canary.bc9ae6c9c.0" + "@material/tab-bar" "15.0.0-canary.bc9ae6c9c.0" + "@material/tab-indicator" "15.0.0-canary.bc9ae6c9c.0" + "@material/tab-scroller" "15.0.0-canary.bc9ae6c9c.0" + "@material/textfield" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tooltip" "15.0.0-canary.bc9ae6c9c.0" + "@material/top-app-bar" "15.0.0-canary.bc9ae6c9c.0" + "@material/touch-target" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.3.0" + +"@angular/platform-browser-dynamic@17.3.1": + version "17.3.1" + resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.3.1.tgz" + integrity sha512-ACW/npNaDxUNQtEomjjv/KIBY8jHEinePff5qosnAxLE0IpA4qE9eDp36zG35xoJqrPJPYjXbZCBRqqrzM7U7Q== + dependencies: + tslib "^2.3.0" + +"@angular/platform-browser@17.3.1": + version "17.3.1" + resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-17.3.1.tgz" + integrity sha512-8ABAL8PElSGzkIparVwifsU0NSu0DdqnWYw9YvLhhZQ6lOuWbG+dTUo/DXzmWhA6ezQWJGNakEZPJJytFIIy+A== + dependencies: + tslib "^2.3.0" + +"@angular/platform-server@17.3.1": + version "17.3.1" + resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-17.3.1.tgz" + integrity sha512-yC1WgUquIac8qFCPMLjRio2ViR3XHexlXKlZpFhqpWAFPsWSHjoCHTEW+KTUFZmOPhUEFR2W8fWOChur8mjthw== + dependencies: + tslib "^2.3.0" + xhr2 "^0.2.0" + +"@angular/router@17.3.1": + version "17.3.1" + resolved "https://registry.yarnpkg.com/@angular/router/-/router-17.3.1.tgz" + integrity sha512-H6H7lY9i5Ppu0SFwwpeWqJbCFw8cILOj8Rd1+AGoCN5m3ivPtjD2Ltz62PI2zZkqx+WhQdk19l61Wm3oRqg70A== + dependencies: + tslib "^2.3.0" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz" + integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== + +"@babel/core@7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.9.tgz" + integrity sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.23.9" + "@babel/parser" "^7.23.9" + "@babel/template" "^7.23.9" + "@babel/traverse" "^7.23.9" + "@babel/types" "^7.23.9" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/core@7.24.0", "@babel/core@^7.12.3": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.0.tgz" + integrity sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.24.0" + "@babel/parser" "^7.24.0" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.0" + "@babel/types" "^7.24.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@7.23.6", "@babel/generator@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz" + integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== + dependencies: + "@babel/types" "^7.23.6" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@7.22.5", "@babel/helper-annotate-as-pure@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz" + integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz" + integrity sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz" + integrity sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-member-expression-to-functions" "^7.22.15" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz" + integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + regexpu-core "^5.3.1" + semver "^6.3.1" + +"@babel/helper-define-polyfill-provider@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz" + integrity sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + +"@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.22.5", "@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-member-expression-to-functions@^7.22.15": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz" + integrity sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA== + dependencies: + "@babel/types" "^7.23.0" + +"@babel/helper-module-imports@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-module-transforms@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/helper-optimise-call-expression@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz" + integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz" + integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== + +"@babel/helper-remap-async-to-generator@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz" + integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-wrap-function" "^7.22.20" + +"@babel/helper-replace-supers@^7.22.20", "@babel/helper-replace-supers@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz" + integrity sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.22.15" + "@babel/helper-optimise-call-expression" "^7.22.5" + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz" + integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@7.22.6", "@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + +"@babel/helper-wrap-function@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz" + integrity sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw== + dependencies: + "@babel/helper-function-name" "^7.22.5" + "@babel/template" "^7.22.15" + "@babel/types" "^7.22.19" + +"@babel/helpers@^7.23.9", "@babel/helpers@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.0.tgz" + integrity sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA== + dependencies: + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.0" + "@babel/types" "^7.24.0" + +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.14.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.0.tgz" + integrity sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg== + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz" + integrity sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz" + integrity sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.23.3" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.23.7": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz" + integrity sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-import-assertions@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz" + integrity sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-import-attributes@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz" + integrity sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz" + integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-arrow-functions@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz" + integrity sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-async-generator-functions@7.23.9", "@babel/plugin-transform-async-generator-functions@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz" + integrity sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.20" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-transform-async-to-generator@7.23.3", "@babel/plugin-transform-async-to-generator@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz" + integrity sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw== + dependencies: + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.20" + +"@babel/plugin-transform-block-scoped-functions@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz" + integrity sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-block-scoping@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz" + integrity sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-class-properties@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz" + integrity sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-class-static-block@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz" + integrity sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-transform-classes@^7.23.8": + version "7.23.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz" + integrity sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.20" + "@babel/helper-split-export-declaration" "^7.22.6" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz" + integrity sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/template" "^7.22.15" + +"@babel/plugin-transform-destructuring@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz" + integrity sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-dotall-regex@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz" + integrity sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-duplicate-keys@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz" + integrity sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-dynamic-import@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz" + integrity sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-transform-exponentiation-operator@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz" + integrity sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-export-namespace-from@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz" + integrity sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-transform-for-of@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz" + integrity sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + +"@babel/plugin-transform-function-name@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz" + integrity sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw== + dependencies: + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-json-strings@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz" + integrity sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-transform-literals@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz" + integrity sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-logical-assignment-operators@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz" + integrity sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-transform-member-expression-literals@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz" + integrity sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-modules-amd@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz" + integrity sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-modules-commonjs@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz" + integrity sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-simple-access" "^7.22.5" + +"@babel/plugin-transform-modules-systemjs@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz" + integrity sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw== + dependencies: + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/plugin-transform-modules-umd@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz" + integrity sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz" + integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-new-target@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz" + integrity sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz" + integrity sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-transform-numeric-separator@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz" + integrity sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-transform-object-rest-spread@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.0.tgz" + integrity sha512-y/yKMm7buHpFFXfxVFS4Vk1ToRJDilIa6fKRioB9Vjichv58TDGXTvqV0dN7plobAmTW5eSEGXDngE+Mm+uO+w== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.23.3" + +"@babel/plugin-transform-object-super@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz" + integrity sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.20" + +"@babel/plugin-transform-optional-catch-binding@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz" + integrity sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-transform-optional-chaining@^7.23.3", "@babel/plugin-transform-optional-chaining@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz" + integrity sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-transform-parameters@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz" + integrity sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-private-methods@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz" + integrity sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-private-property-in-object@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz" + integrity sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-transform-property-literals@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz" + integrity sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-regenerator@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz" + integrity sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + regenerator-transform "^0.15.2" + +"@babel/plugin-transform-reserved-words@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz" + integrity sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-runtime@7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.0.tgz" + integrity sha512-zc0GA5IitLKJrSfXlXmp8KDqLrnGECK7YRfQBmEKg1NmBOQ7e+KuclBEKJgzifQeUYLdNiAw4B4bjyvzWVLiSA== + dependencies: + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + babel-plugin-polyfill-corejs2 "^0.4.8" + babel-plugin-polyfill-corejs3 "^0.9.0" + babel-plugin-polyfill-regenerator "^0.5.5" + semver "^6.3.1" + +"@babel/plugin-transform-shorthand-properties@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz" + integrity sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-spread@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz" + integrity sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + +"@babel/plugin-transform-sticky-regex@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz" + integrity sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-template-literals@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz" + integrity sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-typeof-symbol@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz" + integrity sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-escapes@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz" + integrity sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-property-regex@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz" + integrity sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-regex@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz" + integrity sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-sets-regex@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz" + integrity sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/preset-env@7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.0.tgz" + integrity sha512-ZxPEzV9IgvGn73iK0E6VB9/95Nd7aMFpbE0l8KQFDG70cOV9IxRP7Y2FUPmlK0v6ImlLqYX50iuZ3ZTVhOF2lA== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.23.3" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.23.3" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.23.7" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.23.3" + "@babel/plugin-syntax-import-attributes" "^7.23.3" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.23.3" + "@babel/plugin-transform-async-generator-functions" "^7.23.9" + "@babel/plugin-transform-async-to-generator" "^7.23.3" + "@babel/plugin-transform-block-scoped-functions" "^7.23.3" + "@babel/plugin-transform-block-scoping" "^7.23.4" + "@babel/plugin-transform-class-properties" "^7.23.3" + "@babel/plugin-transform-class-static-block" "^7.23.4" + "@babel/plugin-transform-classes" "^7.23.8" + "@babel/plugin-transform-computed-properties" "^7.23.3" + "@babel/plugin-transform-destructuring" "^7.23.3" + "@babel/plugin-transform-dotall-regex" "^7.23.3" + "@babel/plugin-transform-duplicate-keys" "^7.23.3" + "@babel/plugin-transform-dynamic-import" "^7.23.4" + "@babel/plugin-transform-exponentiation-operator" "^7.23.3" + "@babel/plugin-transform-export-namespace-from" "^7.23.4" + "@babel/plugin-transform-for-of" "^7.23.6" + "@babel/plugin-transform-function-name" "^7.23.3" + "@babel/plugin-transform-json-strings" "^7.23.4" + "@babel/plugin-transform-literals" "^7.23.3" + "@babel/plugin-transform-logical-assignment-operators" "^7.23.4" + "@babel/plugin-transform-member-expression-literals" "^7.23.3" + "@babel/plugin-transform-modules-amd" "^7.23.3" + "@babel/plugin-transform-modules-commonjs" "^7.23.3" + "@babel/plugin-transform-modules-systemjs" "^7.23.9" + "@babel/plugin-transform-modules-umd" "^7.23.3" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" + "@babel/plugin-transform-new-target" "^7.23.3" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.23.4" + "@babel/plugin-transform-numeric-separator" "^7.23.4" + "@babel/plugin-transform-object-rest-spread" "^7.24.0" + "@babel/plugin-transform-object-super" "^7.23.3" + "@babel/plugin-transform-optional-catch-binding" "^7.23.4" + "@babel/plugin-transform-optional-chaining" "^7.23.4" + "@babel/plugin-transform-parameters" "^7.23.3" + "@babel/plugin-transform-private-methods" "^7.23.3" + "@babel/plugin-transform-private-property-in-object" "^7.23.4" + "@babel/plugin-transform-property-literals" "^7.23.3" + "@babel/plugin-transform-regenerator" "^7.23.3" + "@babel/plugin-transform-reserved-words" "^7.23.3" + "@babel/plugin-transform-shorthand-properties" "^7.23.3" + "@babel/plugin-transform-spread" "^7.23.3" + "@babel/plugin-transform-sticky-regex" "^7.23.3" + "@babel/plugin-transform-template-literals" "^7.23.3" + "@babel/plugin-transform-typeof-symbol" "^7.23.3" + "@babel/plugin-transform-unicode-escapes" "^7.23.3" + "@babel/plugin-transform-unicode-property-regex" "^7.23.3" + "@babel/plugin-transform-unicode-regex" "^7.23.3" + "@babel/plugin-transform-unicode-sets-regex" "^7.23.3" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.8" + babel-plugin-polyfill-corejs3 "^0.9.0" + babel-plugin-polyfill-regenerator "^0.5.5" + core-js-compat "^3.31.0" + semver "^6.3.1" + +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/regjsgen@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz" + integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== + +"@babel/runtime@7.24.0", "@babel/runtime@^7.21.0", "@babel/runtime@^7.8.4": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz" + integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.22.15", "@babel/template@^7.23.9", "@babel/template@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz" + integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + +"@babel/traverse@^7.23.9", "@babel/traverse@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.0.tgz" + integrity sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.23.9", "@babel/types@^7.24.0", "@babel/types@^7.4.4": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz" + integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@discoveryjs/json-ext@0.5.7": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@es-joy/jsdoccomment@~0.41.0": + version "0.41.0" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.41.0.tgz" + integrity sha512-aKUhyn1QI5Ksbqcr3fFJj16p99QdjUxXAEuFst1Z47DRyoiMwivIH9MV/ARcJOCXVjPfjITciej8ZD2O/6qUmw== + dependencies: + comment-parser "1.4.1" + esquery "^1.5.0" + jsdoc-type-pratt-parser "~4.0.0" + +"@esbuild/aix-ppc64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz#d1bc06aedb6936b3b6d313bf809a5a40387d2b7f" + integrity sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA== + +"@esbuild/aix-ppc64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz#eafa8775019b3650a77e8310ba4dbd17ca7af6d5" + integrity sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA== + +"@esbuild/android-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz#7ad65a36cfdb7e0d429c353e00f680d737c2aed4" + integrity sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA== + +"@esbuild/android-arm64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz#68791afa389550736f682c15b963a4f37ec2f5f6" + integrity sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A== + +"@esbuild/android-arm@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.12.tgz#b0c26536f37776162ca8bde25e42040c203f2824" + integrity sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w== + +"@esbuild/android-arm@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.1.tgz#38c91d8ee8d5196f7fbbdf4f0061415dde3a473a" + integrity sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw== + +"@esbuild/android-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.12.tgz#cb13e2211282012194d89bf3bfe7721273473b3d" + integrity sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew== + +"@esbuild/android-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.1.tgz#93f6190ce997b313669c20edbf3645fc6c8d8f22" + integrity sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA== + +"@esbuild/darwin-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz" + integrity sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g== + +"@esbuild/darwin-arm64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz" + integrity sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA== + +"@esbuild/darwin-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz#e37d9633246d52aecf491ee916ece709f9d5f4cd" + integrity sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A== + +"@esbuild/darwin-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz#92504077424584684862f483a2242cfde4055ba2" + integrity sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA== + +"@esbuild/freebsd-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz#1ee4d8b682ed363b08af74d1ea2b2b4dbba76487" + integrity sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA== + +"@esbuild/freebsd-arm64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz#a1646fa6ba87029c67ac8a102bb34384b9290774" + integrity sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw== + +"@esbuild/freebsd-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz#37a693553d42ff77cd7126764b535fb6cc28a11c" + integrity sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg== + +"@esbuild/freebsd-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz#41c9243ab2b3254ea7fb512f71ffdb341562e951" + integrity sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg== + +"@esbuild/linux-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz#be9b145985ec6c57470e0e051d887b09dddb2d4b" + integrity sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA== + +"@esbuild/linux-arm64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz#f3c1e1269fbc9eedd9591a5bdd32bf707a883156" + integrity sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w== + +"@esbuild/linux-arm@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz#207ecd982a8db95f7b5279207d0ff2331acf5eef" + integrity sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w== + +"@esbuild/linux-arm@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz#4503ca7001a8ee99589c072801ce9d7540717a21" + integrity sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw== + +"@esbuild/linux-ia32@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz#d0d86b5ca1562523dc284a6723293a52d5860601" + integrity sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA== + +"@esbuild/linux-ia32@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz#98c474e3e0cbb5bcbdd8561a6e65d18f5767ce48" + integrity sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw== + +"@esbuild/linux-loong64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz#9a37f87fec4b8408e682b528391fa22afd952299" + integrity sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA== + +"@esbuild/linux-loong64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz#a8097d28d14b9165c725fe58fc438f80decd2f33" + integrity sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA== + +"@esbuild/linux-mips64el@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz#4ddebd4e6eeba20b509d8e74c8e30d8ace0b89ec" + integrity sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w== + +"@esbuild/linux-mips64el@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz#c44f6f0d7d017c41ad3bb15bfdb69b690656b5ea" + integrity sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA== + +"@esbuild/linux-ppc64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz#adb67dadb73656849f63cd522f5ecb351dd8dee8" + integrity sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg== + +"@esbuild/linux-ppc64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz#0765a55389a99237b3c84227948c6e47eba96f0d" + integrity sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw== + +"@esbuild/linux-riscv64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz#11bc0698bf0a2abf8727f1c7ace2112612c15adf" + integrity sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg== + +"@esbuild/linux-riscv64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz#e4153b032288e3095ddf4c8be07893781b309a7e" + integrity sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg== + +"@esbuild/linux-s390x@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz#e86fb8ffba7c5c92ba91fc3b27ed5a70196c3cc8" + integrity sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg== + +"@esbuild/linux-s390x@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz#b9ab8af6e4b73b26d63c1c426d7669a5d53eb5a7" + integrity sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ== + +"@esbuild/linux-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz#5f37cfdc705aea687dfe5dfbec086a05acfe9c78" + integrity sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg== + +"@esbuild/linux-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz#0b25da17ac38c3e11cdd06ca3691d4d6bef2755f" + integrity sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA== + +"@esbuild/netbsd-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz#29da566a75324e0d0dd7e47519ba2f7ef168657b" + integrity sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA== + +"@esbuild/netbsd-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz#3148e48406cd0d4f7ba1e0bf3f4d77d548c98407" + integrity sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg== + +"@esbuild/openbsd-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz#306c0acbdb5a99c95be98bdd1d47c916e7dc3ff0" + integrity sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw== + +"@esbuild/openbsd-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz#7b73e852986a9750192626d377ac96ac2b749b76" + integrity sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw== + +"@esbuild/sunos-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz#0933eaab9af8b9b2c930236f62aae3fc593faf30" + integrity sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA== + +"@esbuild/sunos-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz#402a441cdac2eee98d8be378c7bc23e00c1861c5" + integrity sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q== + +"@esbuild/win32-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz#773bdbaa1971b36db2f6560088639ccd1e6773ae" + integrity sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A== + +"@esbuild/win32-arm64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz#36c4e311085806a6a0c5fc54d1ac4d7b27e94d7b" + integrity sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A== + +"@esbuild/win32-ia32@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz#000516cad06354cc84a73f0943a4aa690ef6fd67" + integrity sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ== + +"@esbuild/win32-ia32@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz#0cf933be3fb9dc58b45d149559fe03e9e22b54fe" + integrity sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw== + +"@esbuild/win32-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz#c57c8afbb4054a3ab8317591a0b7320360b444ae" + integrity sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA== + +"@esbuild/win32-x64@0.20.1": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz#77583b6ea54cee7c1410ebbd54051b6a3fcbd8ba" + integrity sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA== + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + +"@fortawesome/fontawesome-free@6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz" + integrity sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw== + +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz" + integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/source-map@^0.3.3": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz" + integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@kurkle/color@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz" + integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw== + +"@leichtgewicht/ip-codec@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz" + integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== + +"@ljharb/through@^2.3.12": + version "2.3.12" + resolved "https://registry.yarnpkg.com/@ljharb/through/-/through-2.3.12.tgz" + integrity sha512-ajo/heTlG3QgC8EGP6APIejksVAYt4ayz4tqoP3MolFELzcH1x1fzwEYRJTPO0IELutZ5HQ0c26/GqAYy79u3g== + dependencies: + call-bind "^1.0.5" + +"@material/animation@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/animation/-/animation-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-leRf+BcZTfC/iSigLXnYgcHAGvFVQveoJT5+2PIRdyPI/bIG7hhciRgacHRsCKC0sGya81dDblLgdkjSUemYLw== + dependencies: + tslib "^2.1.0" + +"@material/auto-init@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/auto-init/-/auto-init-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-uxzDq7q3c0Bu1pAsMugc1Ik9ftQYQqZY+5e2ybNplT8gTImJhNt4M2mMiMHbMANk2l3UgICmUyRSomgPBWCPIA== + dependencies: + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/banner@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/banner/-/banner-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-SHeVoidCUFVhXANN6MNWxK9SZoTSgpIP8GZB7kAl52BywLxtV+FirTtLXkg/8RUkxZRyRWl7HvQ0ZFZa7QQAyA== + dependencies: + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/button" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/base@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/base/-/base-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-Fc3vGuOf+duGo22HTRP6dHdc+MUe0VqQfWOuKrn/wXKD62m0QQR2TqJd3rRhCumH557T5QUyheW943M3E+IGfg== + dependencies: + tslib "^2.1.0" + +"@material/button@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/button/-/button-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-3AQgwrPZCTWHDJvwgKq7Cj+BurQ4wTjDdGL+FEnIGUAjJDskwi1yzx5tW2Wf/NxIi7IoPFyOY3UB41jwMiOrnw== + dependencies: + "@material/density" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/focus-ring" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + "@material/touch-target" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/card@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/card/-/card-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-nPlhiWvbLmooTnBmV5gmzB0eLWSgLKsSRBYAbIBmO76Okgz1y+fQNLag+lpm/TDaHVsn5fmQJH8e0zIg0rYsQA== + dependencies: + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/checkbox@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/checkbox/-/checkbox-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-4tpNnO1L0IppoMF3oeQn8F17t2n0WHB0D7mdJK9rhrujen/fLbekkIC82APB3fdGtLGg3qeNqDqPsJm1YnmrwA== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/density" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/focus-ring" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/touch-target" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/chips@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/chips/-/chips-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-fqHKvE5bSWK0bXVkf57MWxZtytGqYBZvvHIOs4JI9HPHEhaJy4CpSw562BEtbm3yFxxALoQknvPW2KYzvADnmA== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/checkbox" "15.0.0-canary.bc9ae6c9c.0" + "@material/density" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/focus-ring" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + "@material/touch-target" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + safevalues "^0.3.4" + tslib "^2.1.0" + +"@material/circular-progress@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/circular-progress/-/circular-progress-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-Lxe8BGAxQwCQqrLhrYrIP0Uok10h7aYS3RBXP41ph+5GmwJd5zdyE2t93qm2dyThvU6qKuXw9726Dtq/N+wvZQ== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/progress-indicator" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/data-table@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/data-table/-/data-table-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-j/7qplT9+sUpfe4pyWhPbl01qJA+OoNAG3VMJruBBR461ZBKyTi7ssKH9yksFGZ8eCEPkOsk/+kDxsiZvRWkeQ== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/checkbox" "15.0.0-canary.bc9ae6c9c.0" + "@material/density" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/icon-button" "15.0.0-canary.bc9ae6c9c.0" + "@material/linear-progress" "15.0.0-canary.bc9ae6c9c.0" + "@material/list" "15.0.0-canary.bc9ae6c9c.0" + "@material/menu" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/select" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + "@material/touch-target" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/density@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/density/-/density-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-Zt3u07fXrBWLW06Tl5fgvjicxNQMkFdawLyNTzZ5TvbXfVkErILLePwwGaw8LNcvzqJP6ABLA8jiR+sKNoJQCg== + dependencies: + tslib "^2.1.0" + +"@material/dialog@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/dialog/-/dialog-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-o+9a/fmwJ9+gY3Z/uhj/PMVJDq7it1NTWKJn2GwAKdB+fDkT4hb9qEdcxMPyvJJ5ups+XiKZo03+tZrD+38c1w== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/button" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/icon-button" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + "@material/touch-target" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/dom@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/dom/-/dom-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-ly78R7aoCJtundSUu0UROU+5pQD5Piae0Y1MkN6bs0724azeazX1KeXFeaf06JOXnlr5/41ol+fSUPowjoqnOg== + dependencies: + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/drawer@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/drawer/-/drawer-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-PFL4cEFnt7VTxDsuspFVNhsFDYyumjU0VWfj3PWB7XudsEfQ3lo85D3HCEtTTbRsCainGN8bgYNDNafLBqiigw== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/list" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/elevation@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/elevation/-/elevation-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-Ro+Pk8jFuap+T0B0shA3xI1hs2b89dNQ2EIPCNjNMp87emHKAzJfhKb7EZGIwv3+gFLlVaLyIVkb94I89KLsyg== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/fab@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/fab/-/fab-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-dvU0KWMRglwJEQwmQtFAmJcAjzg9VFF6Aqj78bJYu/DAIGFJ1VTTTSgoXM/XCm1YyQEZ7kZRvxBO37CH54rSDg== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/focus-ring" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + "@material/touch-target" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/feature-targeting@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/feature-targeting/-/feature-targeting-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-wkDjVcoVEYYaJvun28IXdln/foLgPD7n9ZC9TY76GErGCwTq+HWpU6wBAAk+ePmpRFDayw4vI4wBlaWGxLtysQ== + dependencies: + tslib "^2.1.0" + +"@material/floating-label@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/floating-label/-/floating-label-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-bUWPtXzZITOD/2mkvLkEPO1ngDWmb74y0Kgbz6llHLOQBtycyJIpuoQJ1q2Ez0NM/tFLwPphhAgRqmL3YQ/Kzw== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/focus-ring@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/focus-ring/-/focus-ring-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-cZHThVose3GvAlJzpJoBI1iqL6d1/Jj9hXrR+r8Mwtb1hBIUEG3hxfsRd4vGREuzROPlf0OgNf/V+YHoSwgR5w== + dependencies: + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + +"@material/form-field@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/form-field/-/form-field-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-+JFXy5X44Gue1CbZZAQ6YejnI203lebYwL0i6k0ylDpWHEOdD5xkF2PyHR28r9/65Ebcbwbff6q7kI1SGoT7MA== + dependencies: + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/icon-button@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/icon-button/-/icon-button-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-1a0MHgyIwOs4RzxrVljsqSizGYFlM1zY2AZaLDsgT4G3kzsplTx8HZQ022GpUCjAygW+WLvg4z1qAhQHvsbqlw== + dependencies: + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/density" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/focus-ring" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/touch-target" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/image-list@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/image-list/-/image-list-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-WKWmiYap2iu4QdqmeUSliLlN4O2Ueqa0OuVAYHn/TCzmQ2xmnhZ1pvDLbs6TplpOmlki7vFfe+aSt5SU9gwfOQ== + dependencies: + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/layout-grid@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/layout-grid/-/layout-grid-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-5GqmT6oTZhUGWIb+CLD0ZNyDyTiJsr/rm9oRIi3+vCujACwxFkON9tzBlZohdtFS16nuzUusthN6Jt9UrJcN6Q== + dependencies: + tslib "^2.1.0" + +"@material/line-ripple@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/line-ripple/-/line-ripple-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-8S30WXEuUdgDdBulzUDlPXD6qMzwCX9SxYb5mGDYLwl199cpSGdXHtGgEcCjokvnpLhdZhcT1Dsxeo1g2Evh5Q== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/linear-progress@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/linear-progress/-/linear-progress-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-6EJpjrz6aoH2/gXLg9iMe0yF2C42hpQyZoHpmcgTLKeci85ktDvJIjwup8tnk8ULQyFiGiIrhXw2v2RSsiFjvQ== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/progress-indicator" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/list@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/list/-/list-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-TQ1ppqiCMQj/P7bGD4edbIIv4goczZUoiUAaPq/feb1dflvrFMzYqJ7tQRRCyBL8nRhJoI2x99tk8Q2RXvlGUQ== + dependencies: + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/density" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/menu-surface@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/menu-surface/-/menu-surface-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-dMtSPN+olTWE+08M5qe4ea1IZOhVryYqzK0Gyb2u1G75rSArUxCOB5rr6OC/ST3Mq3RS6zGuYo7srZt4534K9Q== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/menu@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/menu/-/menu-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-IlAh61xzrzxXs38QZlt74UYt8J431zGznSzDtB1Fqs6YFNd11QPKoiRXn1J2Qu/lUxbFV7i8NBKMCKtia0n6/Q== + dependencies: + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/list" "15.0.0-canary.bc9ae6c9c.0" + "@material/menu-surface" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/notched-outline@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/notched-outline/-/notched-outline-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-WuurMg44xexkvLTBTnsO0A+qnzFjpcPdvgWBGstBepYozsvSF9zJGdb1x7Zv1MmqbpYh/Ohnuxtb/Y3jOh6irg== + dependencies: + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/floating-label" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/progress-indicator@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/progress-indicator/-/progress-indicator-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-uOnsvqw5F2fkeTnTl4MrYzjI7KCLmmLyZaM0cgLNuLsWVlddQE+SGMl28tENx7DUK3HebWq0FxCP8f25LuDD+w== + dependencies: + tslib "^2.1.0" + +"@material/radio@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/radio/-/radio-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-ehzOK+U1IxQN+OQjgD2lsnf1t7t7RAwQzeO6Czkiuid29ookYbQynWuLWk7NW8H8ohl7lnmfqTP1xSNkkL/F0g== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/density" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/focus-ring" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/touch-target" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/ripple@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/ripple/-/ripple-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-JfLW+g3GMVDv4cruQ19+HUxpKVdWCldFlIPw1UYezz2h3WTNDy05S3uP2zUdXzZ01C3dkBFviv4nqZ0GCT16MA== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/rtl@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/rtl/-/rtl-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-SkKLNLFp5QtG7/JEFg9R92qq4MzTcZ5As6sWbH7rRg6ahTHoJEuqE+pOb9Vrtbj84k5gtX+vCYPvCILtSlr2uw== + dependencies: + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/segmented-button@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/segmented-button/-/segmented-button-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-YDwkCWP9l5mIZJ7pZJZ2hMDxfBlIGVJ+deNzr8O+Z7/xC5LGXbl4R5aPtUVHygvXAXxpf5096ZD+dSXzYzvWlw== + dependencies: + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/touch-target" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/select@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/select/-/select-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-unfOWVf7T0sixVG+3k3RTuATfzqvCF6QAzA6J9rlCh/Tq4HuIBNDdV4z19IVu4zwmgWYxY0iSvqWUvdJJYwakQ== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/density" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/floating-label" "15.0.0-canary.bc9ae6c9c.0" + "@material/line-ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/list" "15.0.0-canary.bc9ae6c9c.0" + "@material/menu" "15.0.0-canary.bc9ae6c9c.0" + "@material/menu-surface" "15.0.0-canary.bc9ae6c9c.0" + "@material/notched-outline" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/shape@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/shape/-/shape-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-Dsvr771ZKC46ODzoixLdGwlLEQLfxfLrtnRojXABoZf5G3o9KtJU+J+5Ld5aa960OAsCzzANuaub4iR88b1guA== + dependencies: + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/slider@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/slider/-/slider-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-3AEu+7PwW4DSNLndue47dh2u7ga4hDJRYmuu7wnJCIWJBnLCkp6C92kNc4Rj5iQY2ftJio5aj1gqryluh5tlYg== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/snackbar@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/snackbar/-/snackbar-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-TwwQSYxfGK6mc03/rdDamycND6o+1p61WNd7ElZv1F1CLxB4ihRjbCoH7Qo+oVDaP8CTpjeclka+24RLhQq0mA== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/button" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/icon-button" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/switch@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/switch/-/switch-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-OjUjtT0kRz1ASAsOS+dNzwMwvsjmqy5edK57692qmrP6bL4GblFfBDoiNJ6t0AN4OaKcmL5Hy/xNrTdOZW7Qqw== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/density" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/focus-ring" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + safevalues "^0.3.4" + tslib "^2.1.0" + +"@material/tab-bar@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/tab-bar/-/tab-bar-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-Xmtq0wJGfu5k+zQeFeNsr4bUKv7L+feCmUp/gsapJ655LQKMXOUQZtSv9ZqWOfrCMy55hoF1CzGFV+oN3tyWWQ== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/density" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/tab" "15.0.0-canary.bc9ae6c9c.0" + "@material/tab-indicator" "15.0.0-canary.bc9ae6c9c.0" + "@material/tab-scroller" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/tab-indicator@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/tab-indicator/-/tab-indicator-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-despCJYi1GrDDq7F2hvLQkObHnSLZPPDxnOzU16zJ6FNYvIdszgfzn2HgAZ6pl5hLOexQ8cla6cAqjTDuaJBhQ== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/tab-scroller@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/tab-scroller/-/tab-scroller-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-QWHG/EWxirj4V9u2IHz+OSY9XCWrnNrPnNgEufxAJVUKV/A8ma1DYeFSQqxhX709R8wKGdycJksg0Flkl7Gq7w== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/tab" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/tab@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/tab/-/tab-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-s/L9otAwn/pZwVQZBRQJmPqYeNbjoEbzbjMpDQf/VBG/6dJ+aP03ilIBEkqo8NVnCoChqcdtVCoDNRtbU+yp6w== + dependencies: + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/focus-ring" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/tab-indicator" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/textfield@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/textfield/-/textfield-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-R3qRex9kCaZIAK8DuxPnVC42R0OaW7AB7fsFknDKeTeVQvRcbnV8E+iWSdqTiGdsi6QQHifX8idUrXw+O45zPw== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/density" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/floating-label" "15.0.0-canary.bc9ae6c9c.0" + "@material/line-ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/notched-outline" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/theme@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/theme/-/theme-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-CpUwXGE0dbhxQ45Hu9r9wbJtO/MAlv5ER4tBHA9tp/K+SU+lDgurBE2touFMg5INmdfVNtdumxb0nPPLaNQcUg== + dependencies: + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/tokens@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/tokens/-/tokens-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-nbEuGj05txWz6ZMUanpM47SaAD7soyjKILR+XwDell9Zg3bGhsnexCNXPEz2fD+YgomS+jM5XmIcaJJHg/H93Q== + dependencies: + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + +"@material/tooltip@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/tooltip/-/tooltip-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-UzuXp0b9NuWuYLYpPguxrjbJnCmT/Cco8CkjI/6JajxaeA3o2XEBbQfRMTq8PTafuBjCHTc0b0mQY7rtxUp1Gg== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/button" "15.0.0-canary.bc9ae6c9c.0" + "@material/dom" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/tokens" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + safevalues "^0.3.4" + tslib "^2.1.0" + +"@material/top-app-bar@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/top-app-bar/-/top-app-bar-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-vJWjsvqtdSD5+yQ/9vgoBtBSCvPJ5uF/DVssv8Hdhgs1PYaAcODUi77kdi0+sy/TaWyOsTkQixqmwnFS16zesA== + dependencies: + "@material/animation" "15.0.0-canary.bc9ae6c9c.0" + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/elevation" "15.0.0-canary.bc9ae6c9c.0" + "@material/ripple" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/shape" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + "@material/typography" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/touch-target@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/touch-target/-/touch-target-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-AqYh9fjt+tv4ZE0C6MeYHblS2H+XwLbDl2mtyrK0DOEnCVQk5/l5ImKDfhrUdFWHvS4a5nBM4AA+sa7KaroLoA== + dependencies: + "@material/base" "15.0.0-canary.bc9ae6c9c.0" + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/rtl" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@material/typography@15.0.0-canary.bc9ae6c9c.0": + version "15.0.0-canary.bc9ae6c9c.0" + resolved "https://registry.yarnpkg.com/@material/typography/-/typography-15.0.0-canary.bc9ae6c9c.0.tgz" + integrity sha512-CKsG1zyv34AKPNyZC8olER2OdPII64iR2SzQjpqh1UUvmIFiMPk23LvQ1OnC5aCB14pOXzmVgvJt31r9eNdZ6Q== + dependencies: + "@material/feature-targeting" "15.0.0-canary.bc9ae6c9c.0" + "@material/theme" "15.0.0-canary.bc9ae6c9c.0" + tslib "^2.1.0" + +"@ngtools/webpack@17.3.1": + version "17.3.1" + resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-17.3.1.tgz" + integrity sha512-6qRYFN6DqogZK0ZFrSlhg1OsIWm3lL3m+/Ixoj6/MLLjDBrTtHqmI93vg6P1EKYTH4fWChL7jtv7iS/LSZubgw== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@npmcli/agent@^2.0.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@npmcli/agent/-/agent-2.2.1.tgz" + integrity sha512-H4FrOVtNyWC8MUwL3UfjOsAihHvT1Pe8POj3JvjXhSTJipsZMtgUALCT4mGyYZNxymkUfOw3PUj6dE4QPp6osQ== + dependencies: + agent-base "^7.1.0" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.1" + lru-cache "^10.0.1" + socks-proxy-agent "^8.0.1" + +"@npmcli/fs@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-3.1.0.tgz" + integrity sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w== + dependencies: + semver "^7.3.5" + +"@npmcli/git@^5.0.0": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-5.0.4.tgz" + integrity sha512-nr6/WezNzuYUppzXRaYu/W4aT5rLxdXqEFupbh6e/ovlYFQ8hpu1UUPV3Ir/YTl+74iXl2ZOMlGzudh9ZPUchQ== + dependencies: + "@npmcli/promise-spawn" "^7.0.0" + lru-cache "^10.0.1" + npm-pick-manifest "^9.0.0" + proc-log "^3.0.0" + promise-inflight "^1.0.1" + promise-retry "^2.0.1" + semver "^7.3.5" + which "^4.0.0" + +"@npmcli/installed-package-contents@^2.0.1": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz" + integrity sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ== + dependencies: + npm-bundled "^3.0.0" + npm-normalize-package-bin "^3.0.0" + +"@npmcli/node-gyp@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz" + integrity sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA== + +"@npmcli/package-json@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/package-json/-/package-json-5.0.0.tgz" + integrity sha512-OI2zdYBLhQ7kpNPaJxiflofYIpkNLi+lnGdzqUOfRmCF3r2l1nadcjtCYMJKv/Utm/ZtlffaUuTiAktPHbc17g== + dependencies: + "@npmcli/git" "^5.0.0" + glob "^10.2.2" + hosted-git-info "^7.0.0" + json-parse-even-better-errors "^3.0.0" + normalize-package-data "^6.0.0" + proc-log "^3.0.0" + semver "^7.5.3" + +"@npmcli/promise-spawn@^7.0.0": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-7.0.1.tgz" + integrity sha512-P4KkF9jX3y+7yFUxgcUdDtLy+t4OlDGuEBLNs57AZsfSfg+uV6MLndqGpnl4831ggaEdXwR50XFoZP4VFtHolg== + dependencies: + which "^4.0.0" + +"@npmcli/run-script@^7.0.0": + version "7.0.4" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-7.0.4.tgz" + integrity sha512-9ApYM/3+rBt9V80aYg6tZfzj3UWdiYyCt7gJUD1VJKvWF5nwKDSICXbYIQbspFTq6TOpbsEtIC0LArB8d9PFmg== + dependencies: + "@npmcli/node-gyp" "^3.0.0" + "@npmcli/package-json" "^5.0.0" + "@npmcli/promise-spawn" "^7.0.0" + node-gyp "^10.0.0" + which "^4.0.0" + +"@nrwl/devkit@18.1.1": + version "18.1.1" + resolved "https://registry.yarnpkg.com/@nrwl/devkit/-/devkit-18.1.1.tgz" + integrity sha512-CaOMlgun8D077PwEJOxD3gV/5/Hva+JK+XUI7kwf/RWl4MI3Oe1RzgWjigvony/AapkXadfAAzqmEAg5QD6AcA== + dependencies: + "@nx/devkit" "18.1.1" + +"@nrwl/tao@18.1.1": + version "18.1.1" + resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-18.1.1.tgz" + integrity sha512-6xkDBmXu2tF2ZdUvDtzvDIRS3xYsgzH4X96W0okEzT7vtWCu3mkpn4uybM/Ttsd5bOUfjNkDzTRU2mK7sGEjjA== + dependencies: + nx "18.1.1" + tslib "^2.3.0" + +"@nx/devkit@18.1.1", "@nx/devkit@^17.2.8 || ^18.0.0": + version "18.1.1" + resolved "https://registry.yarnpkg.com/@nx/devkit/-/devkit-18.1.1.tgz" + integrity sha512-loY9rSU9GiZU5+SBHY+zrF9j8/f422NcGVYIlWD4aZUTlEeJzaGVk2JtsLEbwyxWuJ8z+jZG7PkpCOp9tfIlbg== + dependencies: + "@nrwl/devkit" "18.1.1" + ejs "^3.1.7" + enquirer "~2.3.6" + ignore "^5.0.4" + semver "^7.5.3" + tmp "~0.2.1" + tslib "^2.3.0" + yargs-parser "21.1.1" + +"@nx/nx-darwin-arm64@18.1.1": + version "18.1.1" + resolved "https://registry.yarnpkg.com/@nx/nx-darwin-arm64/-/nx-darwin-arm64-18.1.1.tgz" + integrity sha512-66NzEuGekWk+pHfBh7otEQNYkQtkeulwZSi/A+CE+YmxBe7tkUwWldR1fpwblHj9pa9zP5cSA1BBXJYSgm1kug== + +"@nx/nx-darwin-x64@18.1.1": + version "18.1.1" + resolved "https://registry.yarnpkg.com/@nx/nx-darwin-x64/-/nx-darwin-x64-18.1.1.tgz#98ca08ebccd0c77701182537d00ccda82ca372f8" + integrity sha512-Er+NWYPTPBZTfLYKse9SVj028iR/OJ8dUNLYAToft89ZlsO5AwKy0qSFVNQguhtuxex+f0yomUTH/GApHzklGw== + +"@nx/nx-freebsd-x64@18.1.1": + version "18.1.1" + resolved "https://registry.yarnpkg.com/@nx/nx-freebsd-x64/-/nx-freebsd-x64-18.1.1.tgz#276baa52507dbd95bb1300da4e6a4acc437aa51c" + integrity sha512-sRvlLTSO2LTQXFarvytjLzv1O3L7YvADfjYmQr1io5YqWpYL5s1ujUFaLnfGPae45uc9qu9YMagsEYsiCOH04Q== + +"@nx/nx-linux-arm-gnueabihf@18.1.1": + version "18.1.1" + resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-18.1.1.tgz#b07cad6605a4aaaad3d7216151267ef924d395b7" + integrity sha512-yzWYaHJj0Yvztd+gUEfqdBwgZPpayrc1OAxfCxqdajuF7kAmjck4Ufz0xn0rcE8nyLdCZMc2Nz1M92cqhw1u/g== + +"@nx/nx-linux-arm64-gnu@18.1.1": + version "18.1.1" + resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-18.1.1.tgz#3d47d55b52ab4b88129b03dfc670b58b9f91b528" + integrity sha512-RPiO2nTwbWtJyG44Ot1H1kmVbAXn7AIzs5itcS/I+/tvkiyh8SGx4BkjctxYyc8DUAhkjVv6GzZdiT5O0OCshw== + +"@nx/nx-linux-arm64-musl@18.1.1": + version "18.1.1" + resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-18.1.1.tgz#e46ba13f5b006179d5f46a1a7b6a0e217dfac566" + integrity sha512-4t0aTVEIlbMeqpJWi08qrieeo0apxPHevqvpCnuM8BfMNHOvMvKgQNBy7Cd2WOlfdCtgf4RfUlqFHfRUAA7NZA== + +"@nx/nx-linux-x64-gnu@18.1.1": + version "18.1.1" + resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-18.1.1.tgz#6ecf0cfa14389e94686656dcc762ed5e110015f9" + integrity sha512-0zeU1qyQfFKnvnga0/KhOjZnNOUr4iADbMGsrlRL+BmS5jpN+/HXJ43TOucw7N1pittYoGqwhenisAaQ8YQi/Q== + +"@nx/nx-linux-x64-musl@18.1.1": + version "18.1.1" + resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-18.1.1.tgz#3a9c4528074820716acbcace2eed59e5bc199ba6" + integrity sha512-bXqHRjCbZz7SDKFQSYU/2GQhFJzaKsh2+CWEeKjc1nBXVSX+UCrRdfinEqs6Fr6rHRC7q8OBcRtYvI2eAijxzw== + +"@nx/nx-win32-arm64-msvc@18.1.1": + version "18.1.1" + resolved "https://registry.yarnpkg.com/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-18.1.1.tgz#d3a73e1ae9c9535fcd86386804467937d2267372" + integrity sha512-xMR2OF99W4OFDXb4yHjoAhN5Pexw6qjMHCG0wEMVN+WYI+NVLNTTFzectXSjYtpjN2n6oPt6RbcOLRSCotqCFg== + +"@nx/nx-win32-x64-msvc@18.1.1": + version "18.1.1" + resolved "https://registry.yarnpkg.com/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-18.1.1.tgz#84bd634350512de66eb6291073ff03832c1d76c7" + integrity sha512-MD6iUpe03eYl7NUwI5yHVqah3HjcTkb/NRfA/cHzkA0ZORpWjyZvUabV0pEyNN2/euGIvZTq1Zg1eyN0ac+dfg== + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@rollup/rollup-android-arm-eabi@4.9.6": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz#66b8d9cb2b3a474d115500f9ebaf43e2126fe496" + integrity sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg== + +"@rollup/rollup-android-arm64@4.9.6": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz#46327d5b86420d2307946bec1535fdf00356e47d" + integrity sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw== + +"@rollup/rollup-darwin-arm64@4.9.6": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz" + integrity sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw== + +"@rollup/rollup-darwin-x64@4.9.6": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz#a2e6e096f74ccea6e2f174454c26aef6bcdd1274" + integrity sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog== + +"@rollup/rollup-linux-arm-gnueabihf@4.9.6": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz#09fcd4c55a2d6160c5865fec708a8e5287f30515" + integrity sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ== + +"@rollup/rollup-linux-arm64-gnu@4.9.6": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz#19a3c0b6315c747ca9acf86e9b710cc2440f83c9" + integrity sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ== + +"@rollup/rollup-linux-arm64-musl@4.9.6": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz#94aaf95fdaf2ad9335983a4552759f98e6b2e850" + integrity sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ== + +"@rollup/rollup-linux-riscv64-gnu@4.9.6": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz#160510e63f4b12618af4013bddf1761cf9fc9880" + integrity sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA== + +"@rollup/rollup-linux-x64-gnu@4.9.6": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz#5ac5d068ce0726bd0a96ca260d5bd93721c0cb98" + integrity sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw== + +"@rollup/rollup-linux-x64-musl@4.9.6": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz#bafa759ab43e8eab9edf242a8259ffb4f2a57a5d" + integrity sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ== + +"@rollup/rollup-win32-arm64-msvc@4.9.6": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz#1cc3416682e5a20d8f088f26657e6e47f8db468e" + integrity sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA== + +"@rollup/rollup-win32-ia32-msvc@4.9.6": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz#7d2251e1aa5e8a1e47c86891fe4547a939503461" + integrity sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ== + +"@rollup/rollup-win32-x64-msvc@4.9.6": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz#2c1fb69e02a3f1506f52698cfdc3a8b6386df9a6" + integrity sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ== + +"@schematics/angular@17.3.1": + version "17.3.1" + resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-17.3.1.tgz" + integrity sha512-B3TkpjDjZhxX+tUc2ySEHU33x82Da0sssq/EMqQ1PQBHeRMa0ecyCeExjFEs2y57ZuC+QeVTaUt+TW45lLSjQw== + dependencies: + "@angular-devkit/core" "17.3.1" + "@angular-devkit/schematics" "17.3.1" + jsonc-parser "3.2.1" + +"@sigstore/bundle@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@sigstore/bundle/-/bundle-2.2.0.tgz" + integrity sha512-5VI58qgNs76RDrwXNhpmyN/jKpq9evV/7f1XrcqcAfvxDl5SeVY/I5Rmfe96ULAV7/FK5dge9RBKGBJPhL1WsQ== + dependencies: + "@sigstore/protobuf-specs" "^0.3.0" + +"@sigstore/core@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@sigstore/core/-/core-1.0.0.tgz" + integrity sha512-dW2qjbWLRKGu6MIDUTBuJwXCnR8zivcSpf5inUzk7y84zqy/dji0/uahppoIgMoKeR+6pUZucrwHfkQQtiG9Rw== + +"@sigstore/protobuf-specs@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@sigstore/protobuf-specs/-/protobuf-specs-0.3.0.tgz" + integrity sha512-zxiQ66JFOjVvP9hbhGj/F/qNdsZfkGb/dVXSanNRNuAzMlr4MC95voPUBX8//ZNnmv3uSYzdfR/JSkrgvZTGxA== + +"@sigstore/sign@^2.2.3": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@sigstore/sign/-/sign-2.2.3.tgz" + integrity sha512-LqlA+ffyN02yC7RKszCdMTS6bldZnIodiox+IkT8B2f8oRYXCB3LQ9roXeiEL21m64CVH1wyveYAORfD65WoSw== + dependencies: + "@sigstore/bundle" "^2.2.0" + "@sigstore/core" "^1.0.0" + "@sigstore/protobuf-specs" "^0.3.0" + make-fetch-happen "^13.0.0" + +"@sigstore/tuf@^2.3.1": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@sigstore/tuf/-/tuf-2.3.1.tgz" + integrity sha512-9Iv40z652td/QbV0o5n/x25H9w6IYRt2pIGbTX55yFDYlApDQn/6YZomjz6+KBx69rXHLzHcbtTS586mDdFD+Q== + dependencies: + "@sigstore/protobuf-specs" "^0.3.0" + tuf-js "^2.2.0" + +"@sigstore/verify@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@sigstore/verify/-/verify-1.1.0.tgz" + integrity sha512-1fTqnqyTBWvV7cftUUFtDcHPdSox0N3Ub7C0lRyReYx4zZUlNTZjCV+HPy4Lre+r45dV7Qx5JLKvqqsgxuyYfg== + dependencies: + "@sigstore/bundle" "^2.2.0" + "@sigstore/core" "^1.0.0" + "@sigstore/protobuf-specs" "^0.3.0" + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@socket.io/component-emitter@~3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz" + integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== + +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@tufjs/canonical-json@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz" + integrity sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA== + +"@tufjs/models@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tufjs/models/-/models-2.0.0.tgz" + integrity sha512-c8nj8BaOExmZKO2DXhDfegyhSGcG9E/mPN3U13L+/PsoWm1uaGiHHjxqSHQiasDBQwDA3aHuw9+9spYAP1qvvg== + dependencies: + "@tufjs/canonical-json" "2.0.0" + minimatch "^9.0.3" + +"@types/body-parser@*": + version "1.19.5" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bonjour@^3.5.9": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz" + integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== + dependencies: + "@types/node" "*" + +"@types/connect-history-api-fallback@^1.3.5": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.3.tgz" + integrity sha512-6mfQ6iNvhSKCZJoY6sIG3m0pKkdUcweVNOLuBBKvoWGzl2yRxOJcYOTRyLKt3nxXvBLJWa6QkW//tgbIwJehmA== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/cookie@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz" + integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== + +"@types/cors@^2.8.12": + version "2.8.16" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.16.tgz" + integrity sha512-Trx5or1Nyg1Fq138PCuWqoApzvoSLWzZ25ORBiHMbbUT42g578lH1GT4TwYDbiUOLFuDsCkfLneT2105fsFWGg== + dependencies: + "@types/node" "*" + +"@types/dropzone@5.7.8": + version "5.7.8" + resolved "https://registry.yarnpkg.com/@types/dropzone/-/dropzone-5.7.8.tgz" + integrity sha512-+L0/KRMuB8cIiCe5AfF448nGMpY+gHiSakqsqT3plEIfgqSV+gcVs1AkngM9zZG8hi6lgMxy4iYEuGXXmqjYvg== + dependencies: + "@types/jquery" "*" + +"@types/eslint-scope@^3.7.3": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "8.44.7" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.7.tgz" + integrity sha512-f5ORu2hcBbKei97U73mf+l9t4zTGl74IqZ0GQk4oVea/VS8tQZYkUveSYojk+frraAVYId0V2WC9O4PTNru2FQ== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@1.0.5", "@types/estree@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": + version "4.17.41" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz" + integrity sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@*", "@types/express@^4.17.13": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/google-protobuf@3.15.12": + version "3.15.12" + resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.15.12.tgz" + integrity sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ== + +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + +"@types/http-proxy@^1.17.8": + version "1.17.14" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.14.tgz" + integrity sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w== + dependencies: + "@types/node" "*" + +"@types/insert-css@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/insert-css/-/insert-css-2.0.3.tgz" + integrity sha512-QgdWn3Mi9D1qqjCKy4PeC+tCasItQYOTk/S9icmHi8dgPx43aotI3cvyHsDaTN18uyYQ1kq/EIdJI0Dzsnm1Kg== + +"@types/jasmine@5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-5.1.4.tgz" + integrity sha512-px7OMFO/ncXxixDe1zR13V1iycqWae0MxTaw62RpFlksUi5QuNWgQJFkTQjIOvrmutJbI7Fp2Y2N1F6D2R4G6w== + +"@types/jquery@*": + version "3.5.27" + resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.27.tgz" + integrity sha512-TR28Y8ezIGgfyA02UOh9x+Fy16/1qWYAnvtRd2gTBJuccX/vmddyti0MezLkTv7f+OLofVc2T961VPyKv1tXJQ== + dependencies: + "@types/sizzle" "*" + +"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@types/mime@*", "@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/node-forge@^1.3.0": + version "1.3.9" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.9.tgz" + integrity sha512-meK88cx/sTalPSLSoCzkiUB4VPIFHmxtXm5FaaqRDqBX2i/Sy8bJ4odsan0b20RBjPh06dAQ+OTTdnyQyhJZyQ== + dependencies: + "@types/node" "*" + +"@types/node@*", "@types/node@20.11.30", "@types/node@>=10.0.0": + version "20.11.30" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.30.tgz" + integrity sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw== + dependencies: + undici-types "~5.26.4" + +"@types/paper@0.12.3": + version "0.12.3" + resolved "https://registry.yarnpkg.com/@types/paper/-/paper-0.12.3.tgz" + integrity sha512-kU00AqWZcpb6b5Fnnrilurk3NfdkuyZXG4v+L9GF5+6zAfTYIThXDcieCtjdHFl/HlLfqZr5Zbf+tKvGX3FyFw== + dependencies: + paper "*" + +"@types/qs@*": + version "6.9.10" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.10.tgz" + integrity sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + +"@types/semver@^7.5.0": + version "7.5.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz" + integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== + +"@types/send@*": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-index@^1.9.1": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz" + integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== + dependencies: + "@types/express" "*" + +"@types/serve-static@*", "@types/serve-static@^1.13.10": + version "1.15.5" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.5.tgz" + integrity sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ== + dependencies: + "@types/http-errors" "*" + "@types/mime" "*" + "@types/node" "*" + +"@types/sizzle@*": + version "2.3.6" + resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.6.tgz" + integrity sha512-m04Om5Gz6kbjUwAQ7XJJQ30OdEFsSmAVsvn4NYwcTRyMVpKKa1aPuESw1n2CxS5fYkOQv3nHgDKeNa8e76fUkw== + +"@types/sockjs@^0.3.33": + version "0.3.36" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz" + integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== + dependencies: + "@types/node" "*" + +"@types/ws@^8.5.5": + version "8.5.9" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.9.tgz" + integrity sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg== + dependencies: + "@types/node" "*" + +"@typescript-eslint/eslint-plugin@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.3.1.tgz" + integrity sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "7.3.1" + "@typescript-eslint/type-utils" "7.3.1" + "@typescript-eslint/utils" "7.3.1" + "@typescript-eslint/visitor-keys" "7.3.1" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/parser@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.3.1.tgz" + integrity sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw== + dependencies: + "@typescript-eslint/scope-manager" "7.3.1" + "@typescript-eslint/types" "7.3.1" + "@typescript-eslint/typescript-estree" "7.3.1" + "@typescript-eslint/visitor-keys" "7.3.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz" + integrity sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg== + dependencies: + "@typescript-eslint/types" "7.2.0" + "@typescript-eslint/visitor-keys" "7.2.0" + +"@typescript-eslint/scope-manager@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz" + integrity sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag== + dependencies: + "@typescript-eslint/types" "7.3.1" + "@typescript-eslint/visitor-keys" "7.3.1" + +"@typescript-eslint/type-utils@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz" + integrity sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA== + dependencies: + "@typescript-eslint/typescript-estree" "7.2.0" + "@typescript-eslint/utils" "7.2.0" + debug "^4.3.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/type-utils@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.3.1.tgz" + integrity sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw== + dependencies: + "@typescript-eslint/typescript-estree" "7.3.1" + "@typescript-eslint/utils" "7.3.1" + debug "^4.3.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/types@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.2.0.tgz" + integrity sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA== + +"@typescript-eslint/types@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.3.1.tgz" + integrity sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw== + +"@typescript-eslint/typescript-estree@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz" + integrity sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA== + dependencies: + "@typescript-eslint/types" "7.2.0" + "@typescript-eslint/visitor-keys" "7.2.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "9.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/typescript-estree@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz" + integrity sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ== + dependencies: + "@typescript-eslint/types" "7.3.1" + "@typescript-eslint/visitor-keys" "7.3.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "9.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.2.0.tgz" + integrity sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "7.2.0" + "@typescript-eslint/types" "7.2.0" + "@typescript-eslint/typescript-estree" "7.2.0" + semver "^7.5.4" + +"@typescript-eslint/utils@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.3.1.tgz" + integrity sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "7.3.1" + "@typescript-eslint/types" "7.3.1" + "@typescript-eslint/typescript-estree" "7.3.1" + semver "^7.5.4" + +"@typescript-eslint/visitor-keys@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz" + integrity sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A== + dependencies: + "@typescript-eslint/types" "7.2.0" + eslint-visitor-keys "^3.4.1" + +"@typescript-eslint/visitor-keys@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz" + integrity sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw== + dependencies: + "@typescript-eslint/types" "7.3.1" + eslint-visitor-keys "^3.4.1" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +"@vitejs/plugin-basic-ssl@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz" + integrity sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A== + +"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz" + integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== + +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== + +"@webassemblyjs/helper-buffer@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz" + integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA== + +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== + +"@webassemblyjs/helper-wasm-section@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz" + integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.11.6" + +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + +"@webassemblyjs/wasm-edit@^1.11.5": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz" + integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.11.6" + "@webassemblyjs/wasm-gen" "1.11.6" + "@webassemblyjs/wasm-opt" "1.11.6" + "@webassemblyjs/wasm-parser" "1.11.6" + "@webassemblyjs/wast-printer" "1.11.6" + +"@webassemblyjs/wasm-gen@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz" + integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wasm-opt@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz" + integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/wasm-gen" "1.11.6" + "@webassemblyjs/wasm-parser" "1.11.6" + +"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz" + integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wast-printer@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz" + integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +"@yarnpkg/lockfile@1.1.0", "@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + +"@yarnpkg/parsers@3.0.0-rc.46": + version "3.0.0-rc.46" + resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz" + integrity sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q== + dependencies: + js-yaml "^3.10.0" + tslib "^2.4.0" + +"@zkochan/js-yaml@0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz" + integrity sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg== + dependencies: + argparse "^2.0.1" + +abbrev@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz" + integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== + +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-import-assertions@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz" + integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.3.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.0.tgz" + integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA== + +acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: + version "8.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== + +adjust-sourcemap-loader@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz" + integrity sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A== + dependencies: + loader-utils "^2.0.0" + regex-parser "^2.2.11" + +agent-base@^7.0.2, agent-base@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.0.tgz" + integrity sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg== + dependencies: + debug "^4.3.4" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv-formats@2.1.1, ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv-keywords@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@8.12.0, ajv@^8.0.0, ajv@^8.9.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ajv@^6.12.4, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-colors@4.1.3, ansi-colors@^4.1.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +ansi-escapes@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-html-community@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz" + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +any-base@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/any-base/-/any-base-1.1.0.tgz" + integrity sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +are-docs-informative@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/are-docs-informative/-/are-docs-informative-0.0.2.tgz" + integrity sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig== + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +aria-query@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== + dependencies: + dequal "^2.0.3" + +array-buffer-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + dependencies: + call-bind "^1.0.2" + is-array-buffer "^3.0.1" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +array-flatten@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz" + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== + +array-includes@^3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz" + integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + is-string "^1.0.7" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.findlastindex@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz" + integrity sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + get-intrinsic "^1.2.1" + +array.prototype.flat@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +arraybuffer.prototype.slice@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz" + integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + is-array-buffer "^3.0.2" + is-shared-array-buffer "^1.0.2" + +async-each-series@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/async-each-series/-/async-each-series-0.1.1.tgz" + integrity sha512-p4jj6Fws4Iy2m0iCmI2am2ZNZCgbdgE+P8F/8csmn2vx7ixXrO2zGcuNsD46X5uZSVecmkEy/M06X2vG8KD6dQ== + +async@^2.6.0: + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== + dependencies: + lodash "^4.17.14" + +async@^3.2.3: + version "3.2.5" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz" + integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +autoprefixer@10.4.18: + version "10.4.18" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.18.tgz" + integrity sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g== + dependencies: + browserslist "^4.23.0" + caniuse-lite "^1.0.30001591" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + +axios@0.21.4: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== + dependencies: + follow-redirects "^1.14.0" + +axios@1.6.8, axios@^1.6.0: + version "1.6.8" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz" + integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +axobject-query@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.0.0.tgz" + integrity sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw== + dependencies: + dequal "^2.0.3" + +babel-loader@9.1.3: + version "9.1.3" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-9.1.3.tgz" + integrity sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw== + dependencies: + find-cache-dir "^4.0.0" + schema-utils "^4.0.0" + +babel-plugin-istanbul@6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-polyfill-corejs2@^0.4.8: + version "0.4.8" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz" + integrity sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.5.0" + semver "^6.3.1" + +babel-plugin-polyfill-corejs3@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz" + integrity sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.5.0" + core-js-compat "^3.34.0" + +babel-plugin-polyfill-regenerator@^0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz" + integrity sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.5.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +base64id@2.0.0, base64id@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz" + integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz" + integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bl@^4.0.3, bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +body-parser@1.20.2, body-parser@^1.19.0: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +bonjour-service@^1.0.11: + version "1.1.1" + resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.1.1.tgz" + integrity sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg== + dependencies: + array-flatten "^2.1.2" + dns-equal "^1.0.0" + fast-deep-equal "^3.1.3" + multicast-dns "^7.2.5" + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +bootstrap@^4.6.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.6.2.tgz" + integrity sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.2, braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browser-sync-client@^2.29.3: + version "2.29.3" + resolved "https://registry.yarnpkg.com/browser-sync-client/-/browser-sync-client-2.29.3.tgz" + integrity sha512-4tK5JKCl7v/3aLbmCBMzpufiYLsB1+UI+7tUXCCp5qF0AllHy/jAqYu6k7hUF3hYtlClKpxExWaR+rH+ny07wQ== + dependencies: + etag "1.8.1" + fresh "0.5.2" + mitt "^1.1.3" + +browser-sync-ui@^2.29.3: + version "2.29.3" + resolved "https://registry.yarnpkg.com/browser-sync-ui/-/browser-sync-ui-2.29.3.tgz" + integrity sha512-kBYOIQjU/D/3kYtUIJtj82e797Egk1FB2broqItkr3i4eF1qiHbFCG6srksu9gWhfmuM/TNG76jMfzAdxEPakg== + dependencies: + async-each-series "0.1.1" + chalk "4.1.2" + connect-history-api-fallback "^1" + immutable "^3" + server-destroy "1.0.1" + socket.io-client "^4.4.1" + stream-throttle "^0.1.3" + +browser-sync@^2.26.13: + version "2.29.3" + resolved "https://registry.yarnpkg.com/browser-sync/-/browser-sync-2.29.3.tgz" + integrity sha512-NiM38O6XU84+MN+gzspVmXV2fTOoe+jBqIBx3IBdhZrdeURr6ZgznJr/p+hQ+KzkKEiGH/GcC4SQFSL0jV49bg== + dependencies: + browser-sync-client "^2.29.3" + browser-sync-ui "^2.29.3" + bs-recipes "1.3.4" + chalk "4.1.2" + chokidar "^3.5.1" + connect "3.6.6" + connect-history-api-fallback "^1" + dev-ip "^1.0.1" + easy-extender "^2.3.4" + eazy-logger "^4.0.1" + etag "^1.8.1" + fresh "^0.5.2" + fs-extra "3.0.1" + http-proxy "^1.18.1" + immutable "^3" + localtunnel "^2.0.1" + micromatch "^4.0.2" + opn "5.3.0" + portscanner "2.2.0" + raw-body "^2.3.2" + resp-modifier "6.0.2" + rx "4.1.0" + send "0.16.2" + serve-index "1.9.1" + serve-static "1.13.2" + server-destroy "1.0.1" + socket.io "^4.4.1" + ua-parser-js "^1.0.33" + yargs "^17.3.1" + +browserslist@^4.21.10, browserslist@^4.21.5, browserslist@^4.22.2, browserslist@^4.22.3, browserslist@^4.23.0: + version "4.23.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz" + integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== + dependencies: + caniuse-lite "^1.0.30001587" + electron-to-chromium "^1.4.668" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +bs-recipes@1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/bs-recipes/-/bs-recipes-1.3.4.tgz" + integrity sha512-BXvDkqhDNxXEjeGM8LFkSbR+jzmP/CYpCiVKYn+soB1dDldeU15EBNDkwVXndKuX35wnNUaPd0qSoQEAkmQtMw== + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + +builtins@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz" + integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ== + dependencies: + semver "^7.0.0" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +cacache@^18.0.0: + version "18.0.2" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-18.0.2.tgz" + integrity sha512-r3NU8h/P+4lVUHfeRw1dtgQYar3DZMm4/cm2bZgOvrFC/su7budSOeqh52VJIC4U4iG1WWwV6vRW0znqBvxNuw== + dependencies: + "@npmcli/fs" "^3.1.0" + fs-minipass "^3.0.0" + glob "^10.2.2" + lru-cache "^10.0.1" + minipass "^7.0.3" + minipass-collect "^2.0.1" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + p-map "^4.0.0" + ssri "^10.0.0" + tar "^6.1.11" + unique-filename "^3.0.0" + +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz" + integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== + dependencies: + function-bind "^1.1.2" + get-intrinsic "^1.2.1" + set-function-length "^1.1.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001591: + version "1.0.30001599" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz" + integrity sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA== + +chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +chart.js@4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.2.tgz" + integrity sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg== + dependencies: + "@kurkle/color" "^0.3.0" + +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.5.1, chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-cursor@3.1.0, cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-spinners@2.6.1, cli-spinners@^2.5.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz" + integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== + +cli-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz" + integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz" + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colorette@^2.0.10: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +colors@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^2.2.0, commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +comment-parser@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.1.tgz" + integrity sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg== + +common-path-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz" + integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w== + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +concurrently@8.2.2: + version "8.2.2" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-8.2.2.tgz" + integrity sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg== + dependencies: + chalk "^4.1.2" + date-fns "^2.30.0" + lodash "^4.17.21" + rxjs "^7.8.1" + shell-quote "^1.8.1" + spawn-command "0.0.2" + supports-color "^8.1.1" + tree-kill "^1.2.2" + yargs "^17.7.2" + +connect-history-api-fallback@^1, connect-history-api-fallback@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz" + integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== + +connect-history-api-fallback@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz" + integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== + +connect-logger@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/connect-logger/-/connect-logger-0.0.1.tgz" + integrity sha512-kC5FPWpcfgpW5HtICnXbdOAFa4uNilU4ZPmsH6RlXaDVfXLupyUjgI1otpj3kOcsoPpDxknxmcoM0wk0ApsjYQ== + dependencies: + moment "*" + +connect@3.6.6: + version "3.6.6" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.6.tgz" + integrity sha512-OO7axMmPpu/2XuX1+2Yrg0ddju31B6xLZMWkJ5rYBu4YRmRVlOjvlY6kw2FJKiAzyxGwnrDUAG4s1Pf0sbBMCQ== + dependencies: + debug "2.6.9" + finalhandler "1.1.0" + parseurl "~1.3.2" + utils-merge "1.0.1" + +connect@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz" + integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== + dependencies: + debug "2.6.9" + finalhandler "1.1.2" + parseurl "~1.3.3" + utils-merge "1.0.1" + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +convert-source-map@^1.5.1, convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +cookie@~0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + +copy-anything@^2.0.1: + version "2.0.6" + resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.6.tgz" + integrity sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw== + dependencies: + is-what "^3.14.1" + +copy-webpack-plugin@11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz" + integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== + dependencies: + fast-glob "^3.2.11" + glob-parent "^6.0.1" + globby "^13.1.1" + normalize-path "^3.0.0" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + +core-js-compat@^3.31.0, core-js-compat@^3.34.0: + version "3.36.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.36.0.tgz" + integrity sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw== + dependencies: + browserslist "^4.22.3" + +core-js@3.36.1: + version "3.36.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.36.1.tgz" + integrity sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA== + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz" + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + +cors@~2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +cosmiconfig@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz" + integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== + dependencies: + env-paths "^2.2.1" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +critters@0.0.22: + version "0.0.22" + resolved "https://registry.yarnpkg.com/critters/-/critters-0.0.22.tgz" + integrity sha512-NU7DEcQZM2Dy8XTKFHxtdnIM/drE312j2T4PCVaSUcS0oBeyT/NImpRw/Ap0zOr/1SE7SgPK9tGPg1WK/sVakw== + dependencies: + chalk "^4.1.0" + css-select "^5.1.0" + dom-serializer "^2.0.0" + domhandler "^5.0.2" + htmlparser2 "^8.0.2" + postcss "^8.4.23" + postcss-media-query-parser "^0.2.3" + +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +css-loader@6.10.0: + version "6.10.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.10.0.tgz" + integrity sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw== + dependencies: + icss-utils "^5.1.0" + postcss "^8.4.33" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.4" + postcss-modules-scope "^3.1.1" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.2.0" + semver "^7.5.4" + +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +custom-event@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz" + integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg== + +date-fns@^2.30.0: + version "2.30.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz" + integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== + dependencies: + "@babel/runtime" "^7.21.0" + +date-format@^4.0.14: + version "4.0.14" + resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz" + integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg== + +debug@2.6.9, debug@^2.2.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +debug@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + +debug@^3.2.6, debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +default-gateway@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz" + integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== + dependencies: + execa "^5.0.0" + +defaults@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz" + integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== + dependencies: + clone "^1.0.2" + +define-data-property@^1.0.1, define-data-property@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz" + integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz" + integrity sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg== + +detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +dev-ip@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dev-ip/-/dev-ip-1.0.1.tgz" + integrity sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A== + +di@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz" + integrity sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz" + integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== + +dns-packet@^5.2.2: + version "5.6.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz" + integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== + dependencies: + "@leichtgewicht/ip-codec" "^2.0.1" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-serialize@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz" + integrity sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ== + dependencies: + custom-event "~1.0.0" + ent "~2.2.0" + extend "^3.0.0" + void-elements "^2.0.0" + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + +dotenv-expand@~10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-10.0.0.tgz" + integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A== + +dotenv@~16.3.1: + version "16.3.2" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.2.tgz" + integrity sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ== + +dropzone@5.9.3, dropzone@^5.9.0: + version "5.9.3" + resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-5.9.3.tgz" + integrity sha512-Azk8kD/2/nJIuVPK+zQ9sjKMRIpRvNyqn9XwbBHNq+iNuSccbJS6hwm1Woy0pMST0erSo0u4j+KJaodndDk4vA== + +duplexer@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +easy-extender@^2.3.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/easy-extender/-/easy-extender-2.3.4.tgz" + integrity sha512-8cAwm6md1YTiPpOvDULYJL4ZS6WfM5/cTeVVh4JsvyYZAoqlRVUpHL9Gr5Fy7HA6xcSZicUia3DeAgO3Us8E+Q== + dependencies: + lodash "^4.17.10" + +eazy-logger@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/eazy-logger/-/eazy-logger-4.1.0.tgz" + integrity sha512-+mn7lRm+Zf1UT/YaH8WXtpU6PIV2iOjzP6jgKoiaq/VNrjYKp+OHZGe2znaLgDeFkw8cL9ffuaUm+nNnzcYyGw== + dependencies: + chalk "4.1.2" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +ejs@^3.1.7: + version "3.1.9" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.9.tgz" + integrity sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.4.668: + version "1.4.672" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.672.tgz" + integrity sha512-YYCy+goe3UqZqa3MOQCI5Mx/6HdBLzXL/mkbGCEWL3sP3Z1BP9zqAzeD3YEmLZlespYGFtyM8tRp5i2vfaUGCA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +encodeurl@~1.0.1, encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +encoding@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + +end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +engine.io-client@~6.5.2: + version "6.5.3" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.5.3.tgz" + integrity sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.11.0" + xmlhttprequest-ssl "~2.0.0" + +engine.io-parser@~5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.1.tgz" + integrity sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ== + +engine.io@~6.5.2: + version "6.5.4" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.4.tgz" + integrity sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg== + dependencies: + "@types/cookie" "^0.4.1" + "@types/cors" "^2.8.12" + "@types/node" ">=10.0.0" + accepts "~1.3.4" + base64id "2.0.0" + cookie "~0.4.1" + cors "~2.8.5" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.11.0" + +enhanced-resolve@^5.15.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +enquirer@~2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +ent@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz" + integrity sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA== + +entities@^4.2.0, entities@^4.3.0, entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +env-paths@^2.2.0, env-paths@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + +errno@^0.1.1: + version "0.1.8" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.22.1: + version "1.22.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz" + integrity sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA== + dependencies: + array-buffer-byte-length "^1.0.0" + arraybuffer.prototype.slice "^1.0.2" + available-typed-arrays "^1.0.5" + call-bind "^1.0.5" + es-set-tostringtag "^2.0.1" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.2" + get-symbol-description "^1.0.0" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + internal-slot "^1.0.5" + is-array-buffer "^3.0.2" + is-callable "^1.2.7" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-typed-array "^1.1.12" + is-weakref "^1.0.2" + object-inspect "^1.13.1" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.1" + safe-array-concat "^1.0.1" + safe-regex-test "^1.0.0" + string.prototype.trim "^1.2.8" + string.prototype.trimend "^1.0.7" + string.prototype.trimstart "^1.0.7" + typed-array-buffer "^1.0.0" + typed-array-byte-length "^1.0.0" + typed-array-byte-offset "^1.0.0" + typed-array-length "^1.0.4" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.13" + +es-module-lexer@^1.2.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.4.1.tgz" + integrity sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w== + +es-set-tostringtag@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz" + integrity sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q== + dependencies: + get-intrinsic "^1.2.2" + has-tostringtag "^1.0.0" + hasown "^2.0.0" + +es-shim-unscopables@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz" + integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== + dependencies: + hasown "^2.0.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +esbuild-wasm@0.20.1: + version "0.20.1" + resolved "https://registry.yarnpkg.com/esbuild-wasm/-/esbuild-wasm-0.20.1.tgz" + integrity sha512-6v/WJubRsjxBbQdz6izgvx7LsVFvVaGmSdwrFHmEzoVgfXL89hkKPoQHsnVI2ngOkcBUQT9kmAM1hVL1k/Av4A== + +esbuild@0.20.1: + version "0.20.1" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.1.tgz" + integrity sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA== + optionalDependencies: + "@esbuild/aix-ppc64" "0.20.1" + "@esbuild/android-arm" "0.20.1" + "@esbuild/android-arm64" "0.20.1" + "@esbuild/android-x64" "0.20.1" + "@esbuild/darwin-arm64" "0.20.1" + "@esbuild/darwin-x64" "0.20.1" + "@esbuild/freebsd-arm64" "0.20.1" + "@esbuild/freebsd-x64" "0.20.1" + "@esbuild/linux-arm" "0.20.1" + "@esbuild/linux-arm64" "0.20.1" + "@esbuild/linux-ia32" "0.20.1" + "@esbuild/linux-loong64" "0.20.1" + "@esbuild/linux-mips64el" "0.20.1" + "@esbuild/linux-ppc64" "0.20.1" + "@esbuild/linux-riscv64" "0.20.1" + "@esbuild/linux-s390x" "0.20.1" + "@esbuild/linux-x64" "0.20.1" + "@esbuild/netbsd-x64" "0.20.1" + "@esbuild/openbsd-x64" "0.20.1" + "@esbuild/sunos-x64" "0.20.1" + "@esbuild/win32-arm64" "0.20.1" + "@esbuild/win32-ia32" "0.20.1" + "@esbuild/win32-x64" "0.20.1" + +esbuild@^0.19.3: + version "0.19.12" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.12.tgz" + integrity sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg== + optionalDependencies: + "@esbuild/aix-ppc64" "0.19.12" + "@esbuild/android-arm" "0.19.12" + "@esbuild/android-arm64" "0.19.12" + "@esbuild/android-x64" "0.19.12" + "@esbuild/darwin-arm64" "0.19.12" + "@esbuild/darwin-x64" "0.19.12" + "@esbuild/freebsd-arm64" "0.19.12" + "@esbuild/freebsd-x64" "0.19.12" + "@esbuild/linux-arm" "0.19.12" + "@esbuild/linux-arm64" "0.19.12" + "@esbuild/linux-ia32" "0.19.12" + "@esbuild/linux-loong64" "0.19.12" + "@esbuild/linux-mips64el" "0.19.12" + "@esbuild/linux-ppc64" "0.19.12" + "@esbuild/linux-riscv64" "0.19.12" + "@esbuild/linux-s390x" "0.19.12" + "@esbuild/linux-x64" "0.19.12" + "@esbuild/netbsd-x64" "0.19.12" + "@esbuild/openbsd-x64" "0.19.12" + "@esbuild/sunos-x64" "0.19.12" + "@esbuild/win32-arm64" "0.19.12" + "@esbuild/win32-ia32" "0.19.12" + "@esbuild/win32-x64" "0.19.12" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-module-utils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz" + integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== + dependencies: + debug "^3.2.7" + +eslint-plugin-import@latest: + version "2.29.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz" + integrity sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg== + dependencies: + array-includes "^3.1.7" + array.prototype.findlastindex "^1.2.3" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.8.0" + hasown "^2.0.0" + is-core-module "^2.13.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.7" + object.groupby "^1.0.1" + object.values "^1.1.7" + semver "^6.3.1" + tsconfig-paths "^3.14.2" + +eslint-plugin-jsdoc@latest: + version "46.9.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.9.0.tgz" + integrity sha512-UQuEtbqLNkPf5Nr/6PPRCtr9xypXY+g8y/Q7gPa0YK7eDhh0y2lWprXRnaYbW7ACgIUvpDKy9X2bZqxtGzBG9Q== + dependencies: + "@es-joy/jsdoccomment" "~0.41.0" + are-docs-informative "^0.0.2" + comment-parser "1.4.1" + debug "^4.3.4" + escape-string-regexp "^4.0.0" + esquery "^1.5.0" + is-builtin-module "^3.2.1" + semver "^7.5.4" + spdx-expression-parse "^3.0.1" + +eslint-plugin-prefer-arrow@latest: + version "1.2.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-prefer-arrow/-/eslint-plugin-prefer-arrow-1.2.3.tgz" + integrity sha512-J9I5PKCOJretVuiZRGvPQxCbllxGAV/viI20JO3LYblAodofBxyMnZAJ+WGeClHgANnSJberTNoFWWjrWKBuXQ== + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-scope@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.0.0.tgz" + integrity sha512-zj3Byw6jX4TcFCJmxOzLt6iol5FAr9xQyZZSQjEzW2UiCJXLwXdRIKCYVFftnpZckaC9Ps9xlC7jB8tSeWWOaw== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.33.0: + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2, esquery@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@1.8.1, etag@^1.8.1, etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exponential-backoff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz" + integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== + +express@4.18.3, express@^4.17.3: + version "4.18.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.3.tgz" + integrity sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.2" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.5.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +external-editor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@3.3.2, fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + dependencies: + reusify "^1.0.4" + +faye-websocket@^0.11.3: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + +figures@3.2.0, figures@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz" + integrity sha512-ejnvM9ZXYzp6PUPUyQBMBf0Co5VX2gr5H2VQe2Ui2jWXNlxv+PYZo8wpAymJNJdLsG1R4p+M4aynF8KuoUEwRw== + dependencies: + debug "2.6.9" + encodeurl "~1.0.1" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.3.1" + unpipe "~1.0.0" + +finalhandler@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-cache-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-4.0.0.tgz" + integrity sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg== + dependencies: + common-path-prefix "^3.0.0" + pkg-dir "^7.0.0" + +find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +find-up@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-6.3.0.tgz" + integrity sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw== + dependencies: + locate-path "^7.1.0" + path-exists "^5.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +flatted@3.3.1, flatted@^3.2.7, flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + +follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.15.6: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +foreground-child@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz" + integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + +fresh@0.5.2, fresh@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs-extra@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz" + integrity sha512-V3Z3WZWVUYd8hoCL5xfXJCaHWYzmtwW5XWYSlLgERi8PWd8bx1kUHUk8L1BT57e49oKnDDD180mjfrHc1yA9rg== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^3.0.0" + universalify "^0.1.0" + +fs-extra@^11.1.0: + version "11.1.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz" + integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs-minipass@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-3.0.3.tgz" + integrity sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw== + dependencies: + minipass "^7.0.3" + +fs-monkey@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.5.tgz" + integrity sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz" + integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== + dependencies: + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.1, glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@^10.2.2, glob@^10.3.10: + version "10.3.10" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz" + integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.3.5" + minimatch "^9.0.1" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry "^1.10.1" + +glob@^7.1.3, glob@^7.1.4, glob@^7.1.7: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.23.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.23.0.tgz" + integrity sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA== + dependencies: + type-fest "^0.20.2" + +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +globby@^13.1.1: + version "13.2.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz" + integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.3.0" + ignore "^5.2.4" + merge2 "^1.4.1" + slash "^4.0.0" + +google-protobuf@3.21.2: + version "3.21.2" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.2.tgz" + integrity sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA== + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz" + integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg== + dependencies: + get-intrinsic "^1.2.2" + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + +hosted-git-info@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-7.0.1.tgz" + integrity sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA== + dependencies: + lru-cache "^10.0.1" + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz" + integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-entities@^2.3.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.4.0.tgz" + integrity sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +htmlparser2@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz" + integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + entities "^4.4.0" + +http-cache-semantics@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz" + integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz" + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-parser-js@>=0.5.1: + version "0.5.8" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz" + integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== + +http-proxy-agent@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.1.tgz" + integrity sha512-My1KCEPs6A0hb4qCVzYp8iEvA8j8YqcvXLZZH8C9OFuTYpYjHE7N2dtG3mRl1HMD4+VGXpF3XcDVcxGBT7yDZQ== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + +http-proxy-middleware@2.0.6, http-proxy-middleware@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz" + integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== + dependencies: + "@types/http-proxy" "^1.17.8" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +https-proxy-agent@7.0.4, https-proxy-agent@^7.0.1: + version "7.0.4" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz" + integrity sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg== + dependencies: + agent-base "^7.0.2" + debug "4" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.4.24, iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.6.2, iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +icss-utils@^5.0.0, icss-utils@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore-walk@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-6.0.4.tgz" + integrity sha512-t7sv42WkwFkyKbivUCglsQW5YWMskWtbEf4MNKX5u/CCWHKSPzN4FtBQGsQZgCLbxOzpVlcbWVK5KB3auIOjSw== + dependencies: + minimatch "^9.0.0" + +ignore@5.3.1, ignore@^5.0.4, ignore@^5.2.0, ignore@^5.2.4: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +image-size@~0.5.0: + version "0.5.5" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz" + integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ== + +immutable@^3: + version "3.8.2" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz" + integrity sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg== + +immutable@^4.0.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.4.tgz" + integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA== + +import-fresh@^3.2.1, import-fresh@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz" + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== + +ini@4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.2.tgz" + integrity sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw== + +inquirer@9.2.15: + version "9.2.15" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-9.2.15.tgz" + integrity sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg== + dependencies: + "@ljharb/through" "^2.3.12" + ansi-escapes "^4.3.2" + chalk "^5.3.0" + cli-cursor "^3.1.0" + cli-width "^4.1.0" + external-editor "^3.1.0" + figures "^3.2.0" + lodash "^4.17.21" + mute-stream "1.0.0" + ora "^5.4.1" + run-async "^3.0.0" + rxjs "^7.8.1" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wrap-ansi "^6.2.0" + +insert-css@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/insert-css/-/insert-css-2.0.0.tgz" + integrity sha512-xGq5ISgcUP5cvGkS2MMFLtPDBtrtQPSFfC6gA6U8wHKqfjTIMZLZNxOItQnoSjdOzlXOLU/yD32RKC4SvjNbtA== + +internal-slot@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.6.tgz" + integrity sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg== + dependencies: + get-intrinsic "^1.2.2" + hasown "^2.0.0" + side-channel "^1.0.4" + +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +ipaddr.js@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz" + integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ== + +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + is-typed-array "^1.1.10" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.13.0, is-core-module@^2.13.1, is-core-module@^2.8.1: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz" + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== + +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-like@^1.0.3: + version "1.0.8" + resolved "https://registry.yarnpkg.com/is-number-like/-/is-number-like-1.0.8.tgz" + integrity sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA== + dependencies: + lodash.isfinite "^3.3.2" + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== + dependencies: + which-typed-array "^1.1.11" + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-what@^3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.14.1.tgz" + integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA== + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz" + integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isbinaryfile@^4.0.8: + version "4.0.10" + resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz" + integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isexe@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-3.1.1.tgz" + integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +istanbul-lib-coverage@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz" + integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz" + integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + rimraf "^2.6.3" + source-map "^0.6.1" + +istanbul-reports@^3.0.2: + version "3.1.6" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz" + integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jackspeak@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz" + integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jake@^10.8.5: + version "10.8.7" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.7.tgz" + integrity sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +jasmine-core@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-5.1.2.tgz" + integrity sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA== + +jasmine-core@^4.1.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-4.6.0.tgz" + integrity sha512-O236+gd0ZXS8YAjFx8xKaJ94/erqUliEkJTDedyE7iHvv4ZVqi+q+8acJxu05/WJDKm512EUNn809In37nWlAQ== + +jasmine-marbles@^0.9.0: + version "0.9.2" + resolved "https://registry.yarnpkg.com/jasmine-marbles/-/jasmine-marbles-0.9.2.tgz" + integrity sha512-T7RjG4fRsdiGGzbQZ6Kj39qYt6O1/KIcR4FkUNsD3DUGkd/AzpwzN+xtk0DXlLWEz5BaVdK1SzMgQDVw879c4Q== + dependencies: + lodash "^4.17.20" + +jasmine-spec-reporter@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/jasmine-spec-reporter/-/jasmine-spec-reporter-7.0.0.tgz" + integrity sha512-OtC7JRasiTcjsaCBPtMO0Tl8glCejM4J4/dNuOJdA8lBjz4PmWjYQ6pzb0uzpBNAWJMDudYuj9OdXJWqM2QTJg== + dependencies: + colors "1.4.0" + +jest-diff@^29.4.1: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jiti@^1.20.0: + version "1.21.0" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz" + integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== + +jquery@3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz" + integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@4.1.0, js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +js-yaml@^3.10.0, js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + +jsdoc-type-pratt-parser@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz" + integrity sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ== + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-parse-even-better-errors@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz" + integrity sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +json5@^2.1.2, json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonc-parser@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== + +jsonc-parser@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz" + integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA== + +jsonfile@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz" + integrity sha512-oBko6ZHlubVB5mRFkur5vgYR1UyqX+S6Y/oCfLhqNdcc2fYFlDpIoNc7AfKS1KOGcnNAkvsr0grLck9ANM815w== + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonparse@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz" + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== + +karma-chrome-launcher@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz" + integrity sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q== + dependencies: + which "^1.2.1" + +karma-cli@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/karma-cli/-/karma-cli-2.0.0.tgz" + integrity sha512-1Kb28UILg1ZsfqQmeELbPzuEb5C6GZJfVIk0qOr8LNYQuYWmAaqP16WpbpKEjhejDrDYyYOwwJXSZO6u7q5Pvw== + dependencies: + resolve "^1.3.3" + +karma-coverage-istanbul-reporter@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-3.0.3.tgz" + integrity sha512-wE4VFhG/QZv2Y4CdAYWDbMmcAHeS926ZIji4z+FkB2aF/EposRb6DP6G5ncT/wXhqUfAb/d7kZrNKPonbvsATw== + dependencies: + istanbul-lib-coverage "^3.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^3.0.6" + istanbul-reports "^3.0.2" + minimatch "^3.0.4" + +karma-jasmine-html-reporter@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz" + integrity sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ== + +karma-jasmine@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-5.1.0.tgz" + integrity sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ== + dependencies: + jasmine-core "^4.1.0" + +karma-source-map-support@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz" + integrity sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A== + dependencies: + source-map-support "^0.5.5" + +karma@6.4.3: + version "6.4.3" + resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.3.tgz" + integrity sha512-LuucC/RE92tJ8mlCwqEoRWXP38UMAqpnq98vktmS9SznSoUPPUJQbc91dHcxcunROvfQjdORVA/YFviH+Xci9Q== + dependencies: + "@colors/colors" "1.5.0" + body-parser "^1.19.0" + braces "^3.0.2" + chokidar "^3.5.1" + connect "^3.7.0" + di "^0.0.1" + dom-serialize "^2.2.1" + glob "^7.1.7" + graceful-fs "^4.2.6" + http-proxy "^1.18.1" + isbinaryfile "^4.0.8" + lodash "^4.17.21" + log4js "^6.4.1" + mime "^2.5.2" + minimatch "^3.0.4" + mkdirp "^0.5.5" + qjobs "^1.2.0" + range-parser "^1.2.1" + rimraf "^3.0.2" + socket.io "^4.7.2" + source-map "^0.6.1" + tmp "^0.2.1" + ua-parser-js "^0.7.30" + yargs "^16.1.1" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +klona@^2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz" + integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== + +launch-editor@^2.6.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.1.tgz" + integrity sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw== + dependencies: + picocolors "^1.0.0" + shell-quote "^1.8.1" + +less-loader@11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-11.1.0.tgz" + integrity sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug== + dependencies: + klona "^2.0.4" + +less@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/less/-/less-4.2.0.tgz" + integrity sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA== + dependencies: + copy-anything "^2.0.1" + parse-node-version "^1.0.1" + tslib "^2.3.0" + optionalDependencies: + errno "^0.1.1" + graceful-fs "^4.1.2" + image-size "~0.5.0" + make-dir "^2.1.0" + mime "^1.4.1" + needle "^3.1.0" + source-map "~0.6.0" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +license-webpack-plugin@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz" + integrity sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw== + dependencies: + webpack-sources "^3.0.0" + +limiter@^1.0.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz" + integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lines-and-columns@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-2.0.4.tgz" + integrity sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A== + +lite-server@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/lite-server/-/lite-server-2.6.1.tgz" + integrity sha512-d3oyB/C8AU4EwYQHlLxcu6vTQDnCaLb81v1KKNYABmFS5oeJ11A+YxlqtpbTclID1AFddJfcB5klf0q98vYIMw== + dependencies: + browser-sync "^2.26.13" + connect-history-api-fallback "^1.6.0" + connect-logger "^0.0.1" + lodash "^4.17.20" + minimist "^1.2.5" + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +loader-utils@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.1.tgz" + integrity sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw== + +loader-utils@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +localtunnel@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/localtunnel/-/localtunnel-2.0.2.tgz" + integrity sha512-n418Cn5ynvJd7m/N1d9WVJISLJF/ellZnfsLnx8WBWGzxv/ntNcFkJ1o6se5quUhCplfLGBNL5tYHiq5WF3Nug== + dependencies: + axios "0.21.4" + debug "4.3.2" + openurl "1.1.1" + yargs "17.1.1" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +locate-path@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-7.2.0.tgz" + integrity sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA== + dependencies: + p-locate "^6.0.0" + +lodash-es@^4.17.15: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +lodash.isfinite@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz" + integrity sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@^4.0.0, log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +log4js@^6.4.1: + version "6.9.1" + resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.9.1.tgz" + integrity sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g== + dependencies: + date-format "^4.0.14" + debug "^4.3.4" + flatted "^3.2.7" + rfdc "^1.3.0" + streamroller "^3.1.5" + +lru-cache@^10.0.1: + version "10.2.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz" + integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +"lru-cache@^9.1.1 || ^10.0.0": + version "10.0.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.2.tgz" + integrity sha512-Yj9mA8fPiVgOUpByoTZO5pNrcl5Yk37FcSHsUINpAsaBIEZIuqcCclDZJCVxqQShDsmYX8QG63svJiTbOATZwg== + dependencies: + semver "^7.3.5" + +magic-string@0.30.8: + version "0.30.8" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.8.tgz" + integrity sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +make-fetch-happen@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-13.0.0.tgz" + integrity sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A== + dependencies: + "@npmcli/agent" "^2.0.0" + cacache "^18.0.0" + http-cache-semantics "^4.1.1" + is-lambda "^1.0.1" + minipass "^7.0.2" + minipass-fetch "^3.0.0" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.3" + promise-retry "^2.0.1" + ssri "^10.0.0" + +material-symbols@^0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/material-symbols/-/material-symbols-0.17.0.tgz" + integrity sha512-CwDddz58cIH/svt8/wjAegQTp9bfNNIkIPjkkr0MAmA1oMlmUkG+C2ss2/yR22mlDzUX4DnXIq5MrgcpKTXoyA== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +memfs@^3.4.12, memfs@^3.4.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== + dependencies: + fs-monkey "^1.0.4" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz" + integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== + +mime@1.6.0, mime@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mime@^2.5.2: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mini-css-extract-plugin@2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz" + integrity sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA== + dependencies: + schema-utils "^4.0.0" + tapable "^2.2.1" + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimatch@9.0.3, minimatch@^9.0.0, minimatch@^9.0.1, minimatch@^9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +minipass-collect@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-2.0.1.tgz" + integrity sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw== + dependencies: + minipass "^7.0.3" + +minipass-fetch@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-3.0.4.tgz" + integrity sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg== + dependencies: + minipass "^7.0.3" + minipass-sized "^1.0.3" + minizlib "^2.1.2" + optionalDependencies: + encoding "^0.1.13" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-json-stream@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz" + integrity sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg== + dependencies: + jsonparse "^1.3.1" + minipass "^3.0.0" + +minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.2, minipass@^7.0.3: + version "7.0.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz" + integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== + +minizlib@^2.1.1, minizlib@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mitt@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mitt/-/mitt-1.2.0.tgz" + integrity sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw== + +mkdirp@^0.5.5: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +moment@*: + version "2.29.4" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz" + integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== + +mrmime@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.0.tgz" + integrity sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2, ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multicast-dns@^7.2.5: + version "7.2.5" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz" + integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== + dependencies: + dns-packet "^5.2.2" + thunky "^1.0.2" + +mute-stream@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz" + integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +needle@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/needle/-/needle-3.2.0.tgz" + integrity sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ== + dependencies: + debug "^3.2.6" + iconv-lite "^0.6.3" + sax "^1.2.4" + +negotiator@0.6.3, negotiator@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +ng2-charts@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/ng2-charts/-/ng2-charts-5.0.4.tgz" + integrity sha512-AnOZ2KSRw7QjiMMNtXz9tdnO+XrIKP/2MX1TfqEEo2fwFU5c8LFJIYqmkMPkIzAEm/U9y/1psA5TDNmxxjEdgA== + dependencies: + lodash-es "^4.17.15" + tslib "^2.3.0" + +ngx-bootstrap@12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/ngx-bootstrap/-/ngx-bootstrap-12.0.0.tgz" + integrity sha512-6/Hs+FT6peMc+Y2uiOm3IawG06Jh3gLQwwKRBF0U1OMlRbpx4KIyHS9GpZtMevtZaBsCRNfHKiSxwsnvn9wx0Q== + dependencies: + tslib "^2.3.0" + +ngx-dropzone-wrapper@16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/ngx-dropzone-wrapper/-/ngx-dropzone-wrapper-16.0.0.tgz" + integrity sha512-URcormA4mgsgcqASnch2GQ2MK+l6JAaHMvODPJUaKwaHQHHNbSkBL+HejjG3iPzlghDOTKiswrnKbPAFqjQzAg== + dependencies: + dropzone "^5.9.0" + tslib "^2.3.0" + +ngx-pagination@6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/ngx-pagination/-/ngx-pagination-6.0.3.tgz" + integrity sha512-lONjTQ7hFPh1SyhwDrRd5ZwM4NMGQ7bNR6vLrs6mrU0Z8Q1zCcWbf/pvyp4DOlGyd9uyZxRy2wUsSZLeIPjbAw== + dependencies: + tslib "^2.3.0" + +nice-napi@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nice-napi/-/nice-napi-1.0.2.tgz" + integrity sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA== + dependencies: + node-addon-api "^3.0.0" + node-gyp-build "^4.2.2" + +node-addon-api@^3.0.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz" + integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== + +node-forge@^1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + +node-gyp-build@^4.2.2: + version "4.6.1" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.1.tgz" + integrity sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ== + +node-gyp@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-10.0.1.tgz" + integrity sha512-gg3/bHehQfZivQVfqIyy8wTdSymF9yTyP4CJifK73imyNMU8AIGQE2pUa7dNWfmMeG9cDVF2eehiRMv0LC1iAg== + dependencies: + env-paths "^2.2.0" + exponential-backoff "^3.1.1" + glob "^10.3.10" + graceful-fs "^4.2.6" + make-fetch-happen "^13.0.0" + nopt "^7.0.0" + proc-log "^3.0.0" + semver "^7.3.5" + tar "^6.1.2" + which "^4.0.0" + +node-machine-id@1.1.12: + version "1.1.12" + resolved "https://registry.yarnpkg.com/node-machine-id/-/node-machine-id-1.1.12.tgz" + integrity sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ== + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +nopt@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.0.tgz" + integrity sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA== + dependencies: + abbrev "^2.0.0" + +normalize-package-data@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-6.0.0.tgz" + integrity sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg== + dependencies: + hosted-git-info "^7.0.0" + is-core-module "^2.8.1" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +npm-bundled@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-3.0.0.tgz" + integrity sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ== + dependencies: + npm-normalize-package-bin "^3.0.0" + +npm-install-checks@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-6.3.0.tgz" + integrity sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw== + dependencies: + semver "^7.1.1" + +npm-normalize-package-bin@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz" + integrity sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ== + +npm-package-arg@11.0.1, npm-package-arg@^11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-11.0.1.tgz" + integrity sha512-M7s1BD4NxdAvBKUPqqRW957Xwcl/4Zvo8Aj+ANrzvIPzGJZElrH7Z//rSaec2ORcND6FHHLnZeY8qgTpXDMFQQ== + dependencies: + hosted-git-info "^7.0.0" + proc-log "^3.0.0" + semver "^7.3.5" + validate-npm-package-name "^5.0.0" + +npm-packlist@^8.0.0: + version "8.0.2" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-8.0.2.tgz" + integrity sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA== + dependencies: + ignore-walk "^6.0.4" + +npm-pick-manifest@9.0.0, npm-pick-manifest@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-9.0.0.tgz" + integrity sha512-VfvRSs/b6n9ol4Qb+bDwNGUXutpy76x6MARw/XssevE0TnctIKcmklJZM5Z7nqs5z5aW+0S63pgCNbpkUNNXBg== + dependencies: + npm-install-checks "^6.0.0" + npm-normalize-package-bin "^3.0.0" + npm-package-arg "^11.0.0" + semver "^7.3.5" + +npm-registry-fetch@^16.0.0: + version "16.1.0" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-16.1.0.tgz" + integrity sha512-PQCELXKt8Azvxnt5Y85GseQDJJlglTFM9L9U9gkv2y4e9s0k3GVDdOx3YoB6gm2Do0hlkzC39iCGXby+Wve1Bw== + dependencies: + make-fetch-happen "^13.0.0" + minipass "^7.0.2" + minipass-fetch "^3.0.0" + minipass-json-stream "^1.0.1" + minizlib "^2.1.2" + npm-package-arg "^11.0.0" + proc-log "^3.0.0" + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +nx@18.1.1, "nx@^17.2.8 || ^18.0.0": + version "18.1.1" + resolved "https://registry.yarnpkg.com/nx/-/nx-18.1.1.tgz" + integrity sha512-rzvzEwe4bwi8cAm0uYOpMwj6PcIqD2anoOWg81rpb/3nQY+r1Qo2tL2/BWxGFh+WK+Jt7TzBZKcdQFEw5Nm84w== + dependencies: + "@nrwl/tao" "18.1.1" + "@yarnpkg/lockfile" "^1.1.0" + "@yarnpkg/parsers" "3.0.0-rc.46" + "@zkochan/js-yaml" "0.0.6" + axios "^1.6.0" + chalk "^4.1.0" + cli-cursor "3.1.0" + cli-spinners "2.6.1" + cliui "^8.0.1" + dotenv "~16.3.1" + dotenv-expand "~10.0.0" + enquirer "~2.3.6" + figures "3.2.0" + flat "^5.0.2" + fs-extra "^11.1.0" + ignore "^5.0.4" + jest-diff "^29.4.1" + js-yaml "4.1.0" + jsonc-parser "3.2.0" + lines-and-columns "~2.0.3" + minimatch "9.0.3" + node-machine-id "1.1.12" + npm-run-path "^4.0.1" + open "^8.4.0" + ora "5.3.0" + semver "^7.5.3" + string-width "^4.2.3" + strong-log-transformer "^2.1.0" + tar-stream "~2.2.0" + tmp "~0.2.1" + tsconfig-paths "^4.1.2" + tslib "^2.3.0" + yargs "^17.6.2" + yargs-parser "21.1.1" + optionalDependencies: + "@nx/nx-darwin-arm64" "18.1.1" + "@nx/nx-darwin-x64" "18.1.1" + "@nx/nx-freebsd-x64" "18.1.1" + "@nx/nx-linux-arm-gnueabihf" "18.1.1" + "@nx/nx-linux-arm64-gnu" "18.1.1" + "@nx/nx-linux-arm64-musl" "18.1.1" + "@nx/nx-linux-x64-gnu" "18.1.1" + "@nx/nx-linux-x64-musl" "18.1.1" + "@nx/nx-win32-arm64-msvc" "18.1.1" + "@nx/nx-win32-x64-msvc" "18.1.1" + +object-assign@^4: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.1, object-inspect@^1.9.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.fromentries@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.7.tgz" + integrity sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +object.groupby@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.1.tgz" + integrity sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + +object.values@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.7.tgz" + integrity sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz" + integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@8.4.2, open@^8.0.9, open@^8.4.0: + version "8.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz" + integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + +openurl@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/openurl/-/openurl-1.1.1.tgz" + integrity sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA== + +opn@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz" + integrity sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g== + dependencies: + is-wsl "^1.1.0" + +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + +ora@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.3.0.tgz" + integrity sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g== + dependencies: + bl "^4.0.3" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + log-symbols "^4.0.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + +ora@5.4.1, ora@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-limit@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-4.0.0.tgz" + integrity sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ== + dependencies: + yocto-queue "^1.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-locate@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-6.0.0.tgz" + integrity sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw== + dependencies: + p-limit "^4.0.0" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-retry@^4.5.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== + dependencies: + "@types/retry" "0.12.0" + retry "^0.13.1" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pacote@17.0.6: + version "17.0.6" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-17.0.6.tgz" + integrity sha512-cJKrW21VRE8vVTRskJo78c/RCvwJCn1f4qgfxL4w77SOWrTCRcmfkYHlHtS0gqpgjv3zhXflRtgsrUCX5xwNnQ== + dependencies: + "@npmcli/git" "^5.0.0" + "@npmcli/installed-package-contents" "^2.0.1" + "@npmcli/promise-spawn" "^7.0.0" + "@npmcli/run-script" "^7.0.0" + cacache "^18.0.0" + fs-minipass "^3.0.0" + minipass "^7.0.2" + npm-package-arg "^11.0.0" + npm-packlist "^8.0.0" + npm-pick-manifest "^9.0.0" + npm-registry-fetch "^16.0.0" + proc-log "^3.0.0" + promise-retry "^2.0.1" + read-package-json "^7.0.0" + read-package-json-fast "^3.0.0" + sigstore "^2.2.0" + ssri "^10.0.0" + tar "^6.1.11" + +paper@*, paper@0.12.17: + version "0.12.17" + resolved "https://registry.yarnpkg.com/paper/-/paper-0.12.17.tgz" + integrity sha512-oCe+e1C2w8hKIcGoAqUjD0GGxGPv+itrRXlEFUmp3H8tY/NTnHOkYgpJFPGw6OJ8Q1Wa6+RgzlY7Dx/2WWHtkA== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse-node-version@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz" + integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== + +parse5-html-rewriting-stream@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz" + integrity sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg== + dependencies: + entities "^4.3.0" + parse5 "^7.0.0" + parse5-sax-parser "^7.0.0" + +parse5-sax-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz" + integrity sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg== + dependencies: + parse5 "^7.0.0" + +parse5@^7.0.0, parse5@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + +parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-exists@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-5.0.0.tgz" + integrity sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-scurry@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz" + integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== + dependencies: + lru-cache "^9.1.1 || ^10.0.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.1.tgz" + integrity sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +piscina@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/piscina/-/piscina-4.4.0.tgz" + integrity sha512-+AQduEJefrOApE4bV7KRmp3N2JnnyErlVqq4P/jmko4FPz9Z877BCccl/iB3FdrWSUkvbGV9Kan/KllJgat3Vg== + optionalDependencies: + nice-napi "^1.0.2" + +pkg-dir@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-7.0.0.tgz" + integrity sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA== + dependencies: + find-up "^6.3.0" + +popper.js@1.16.1: + version "1.16.1" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz" + integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== + +portscanner@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-2.2.0.tgz" + integrity sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw== + dependencies: + async "^2.6.0" + is-number-like "^1.0.3" + +postcss-loader@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-8.1.1.tgz" + integrity sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ== + dependencies: + cosmiconfig "^9.0.0" + jiti "^1.20.0" + semver "^7.5.4" + +postcss-media-query-parser@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz" + integrity sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig== + +postcss-modules-extract-imports@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz" + integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== + +postcss-modules-local-by-default@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz" + integrity sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz" + integrity sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== + dependencies: + icss-utils "^5.0.0" + +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: + version "6.0.13" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz" + integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@8.4.35, postcss@^8.2.14, postcss@^8.4.23, postcss@^8.4.33, postcss@^8.4.35: + version "8.4.35" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.35.tgz" + integrity sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +proc-log@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-3.0.0.tgz" + integrity sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== + +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz" + integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +qjobs@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz" + integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@^1.2.1, range-parser@~1.2.0, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2, raw-body@^2.3.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + +read-package-json-fast@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz" + integrity sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw== + dependencies: + json-parse-even-better-errors "^3.0.0" + npm-normalize-package-bin "^3.0.0" + +read-package-json@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-7.0.0.tgz" + integrity sha512-uL4Z10OKV4p6vbdvIXB+OzhInYtIozl/VxUBPgNkBuUi2DeRonnuspmaVAMcrkmfjKGNmRndyQAbE7/AmzGwFg== + dependencies: + glob "^10.2.2" + json-parse-even-better-errors "^3.0.0" + normalize-package-data "^6.0.0" + npm-normalize-package-bin "^3.0.0" + +readable-stream@^2.0.1: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +reflect-metadata@0.2.1, reflect-metadata@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.1.tgz" + integrity sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw== + +regenerate-unicode-properties@^10.1.0: + version "10.1.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz" + integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + +regenerator-transform@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz" + integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== + dependencies: + "@babel/runtime" "^7.8.4" + +regex-parser@^2.2.11: + version "2.2.11" + resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.11.tgz" + integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q== + +regexp.prototype.flags@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz" + integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + set-function-name "^2.0.0" + +regexpu-core@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz" + integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== + dependencies: + "@babel/regjsgen" "^0.8.0" + regenerate "^1.4.2" + regenerate-unicode-properties "^10.1.0" + regjsparser "^0.9.1" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== + dependencies: + jsesc "~0.5.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-url-loader@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz" + integrity sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg== + dependencies: + adjust-sourcemap-loader "^4.0.0" + convert-source-map "^1.7.0" + loader-utils "^2.0.0" + postcss "^8.2.14" + source-map "0.6.1" + +resolve@1.22.8, resolve@^1.14.2, resolve@^1.22.4, resolve@^1.3.3: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resp-modifier@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/resp-modifier/-/resp-modifier-6.0.2.tgz" + integrity sha512-U1+0kWC/+4ncRFYqQWTx/3qkfE6a4B/h3XXgmXypfa0SPZ3t7cbbaFk297PjQS/yov24R18h6OZe6iZwj3NSLw== + dependencies: + debug "^2.2.0" + minimatch "^3.0.2" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + +rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rollup@^4.2.0: + version "4.9.6" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.9.6.tgz" + integrity sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg== + dependencies: + "@types/estree" "1.0.5" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.9.6" + "@rollup/rollup-android-arm64" "4.9.6" + "@rollup/rollup-darwin-arm64" "4.9.6" + "@rollup/rollup-darwin-x64" "4.9.6" + "@rollup/rollup-linux-arm-gnueabihf" "4.9.6" + "@rollup/rollup-linux-arm64-gnu" "4.9.6" + "@rollup/rollup-linux-arm64-musl" "4.9.6" + "@rollup/rollup-linux-riscv64-gnu" "4.9.6" + "@rollup/rollup-linux-x64-gnu" "4.9.6" + "@rollup/rollup-linux-x64-musl" "4.9.6" + "@rollup/rollup-win32-arm64-msvc" "4.9.6" + "@rollup/rollup-win32-ia32-msvc" "4.9.6" + "@rollup/rollup-win32-x64-msvc" "4.9.6" + fsevents "~2.3.2" + +run-async@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-3.0.0.tgz" + integrity sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rx@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz" + integrity sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug== + +rxjs@7.8.1, rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + +safe-array-concat@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz" + integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + isarray "^2.0.5" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-regex-test@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz" + integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + is-regex "^1.1.4" + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +safevalues@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/safevalues/-/safevalues-0.3.4.tgz" + integrity sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw== + +sass-loader@14.1.1: + version "14.1.1" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-14.1.1.tgz" + integrity sha512-QX8AasDg75monlybel38BZ49JP5Z+uSKfKwF2rO7S74BywaRmGQMUBw9dtkS+ekyM/QnP+NOrRYq8ABMZ9G8jw== + dependencies: + neo-async "^2.6.2" + +sass@1.71.1: + version "1.71.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.71.1.tgz" + integrity sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" + +sax@^1.2.4: + version "1.3.0" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz" + integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA== + +schema-utils@^3.1.1, schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +schema-utils@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz" + integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz" + integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== + +selfsigned@^2.1.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz" + integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== + dependencies: + "@types/node-forge" "^1.3.0" + node-forge "^1" + +semver@7.6.0, semver@^7.0.0, semver@^7.1.1, semver@^7.3.5, semver@^7.5.3, semver@^7.5.4: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + +semver@^5.6.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +send@0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz" + integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.6.2" + mime "1.4.1" + ms "2.0.0" + on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.4.0" + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz" + integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== + dependencies: + randombytes "^2.1.0" + +serve-index@1.9.1, serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz" + integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz" + integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.2" + send "0.16.2" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +server-destroy@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/server-destroy/-/server-destroy-1.0.1.tgz" + integrity sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ== + +set-function-length@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz" + integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ== + dependencies: + define-data-property "^1.1.1" + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + +set-function-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz" + integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== + dependencies: + define-data-property "^1.0.1" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.0" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz" + integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== + +short-uuid@4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/short-uuid/-/short-uuid-4.2.2.tgz" + integrity sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw== + dependencies: + any-base "^1.1.0" + uuid "^8.3.2" + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +sigstore@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/sigstore/-/sigstore-2.2.2.tgz" + integrity sha512-2A3WvXkQurhuMgORgT60r6pOWiCOO5LlEqY2ADxGBDGVYLSo5HN0uLtb68YpVpuL/Vi8mLTe7+0Dx2Fq8lLqEg== + dependencies: + "@sigstore/bundle" "^2.2.0" + "@sigstore/core" "^1.0.0" + "@sigstore/protobuf-specs" "^0.3.0" + "@sigstore/sign" "^2.2.3" + "@sigstore/tuf" "^2.3.1" + "@sigstore/verify" "^1.1.0" + +simple-color-picker@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/simple-color-picker/-/simple-color-picker-1.0.5.tgz" + integrity sha512-MUsSVfO/51zTrBA6nsmX5ABe/FXmhd6N+hie6EAWb6Ph4eydtQNRZ9hyb/HmLaG403YB/JAtpry70AaCd8AwBw== + dependencies: + "@types/insert-css" "^2.0.0" + insert-css "^2.0.0" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +socket.io-adapter@~2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz" + integrity sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA== + dependencies: + ws "~8.11.0" + +socket.io-client@^4.4.1: + version "4.7.2" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.7.2.tgz" + integrity sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.5.2" + socket.io-parser "~4.2.4" + +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + +socket.io@^4.4.1, socket.io@^4.7.2: + version "4.7.4" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.4.tgz" + integrity sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw== + dependencies: + accepts "~1.3.4" + base64id "~2.0.0" + cors "~2.8.5" + debug "~4.3.2" + engine.io "~6.5.2" + socket.io-adapter "~2.5.2" + socket.io-parser "~4.2.4" + +sockjs@^0.3.24: + version "0.3.24" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== + dependencies: + faye-websocket "^0.11.3" + uuid "^8.3.2" + websocket-driver "^0.7.4" + +socks-proxy-agent@^8.0.1: + version "8.0.2" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz" + integrity sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g== + dependencies: + agent-base "^7.0.2" + debug "^4.3.4" + socks "^2.7.1" + +socks@^2.7.1: + version "2.7.3" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.3.tgz" + integrity sha512-vfuYK48HXCTFD03G/1/zkIls3Ebr2YNa4qU9gHDZdblHLiqhJrJGkY3+0Nx0JpN9qBhJbVObc1CNciT1bIZJxw== + dependencies: + ip-address "^9.0.5" + smart-buffer "^4.2.0" + +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map-loader@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-5.0.0.tgz" + integrity sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA== + dependencies: + iconv-lite "^0.6.3" + source-map-js "^1.0.2" + +source-map-support@0.5.21, source-map-support@^0.5.5, source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +spawn-command@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz" + integrity sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ== + +spdx-correct@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0, spdx-expression-parse@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.16" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz" + integrity sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +ssri@^10.0.0: + version "10.0.5" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-10.0.5.tgz" + integrity sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A== + dependencies: + minipass "^7.0.3" + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +"statuses@>= 1.4.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +statuses@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz" + integrity sha512-wuTCPGlJONk/a1kqZ4fQM2+908lC7fa7nPYpTC1EhnvqLX/IICbeP1OZGDtA374trpSq68YubKUMo8oRhN46yg== + +statuses@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz" + integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew== + +stream-throttle@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/stream-throttle/-/stream-throttle-0.1.3.tgz" + integrity sha512-889+B9vN9dq7/vLbGyuHeZ6/ctf5sNuGWsDy89uNxkFTAgzy0eK7+w5fL3KLNRTkLle7EgZGvHUphZW0Q26MnQ== + dependencies: + commander "^2.2.0" + limiter "^1.0.5" + +streamroller@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.1.5.tgz" + integrity sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw== + dependencies: + date-format "^4.0.14" + debug "^4.3.4" + fs-extra "^8.1.0" + +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string.prototype.trim@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz" + integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +string.prototype.trimend@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz" + integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +string.prototype.trimstart@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@3.1.1, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strong-log-transformer@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz" + integrity sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA== + dependencies: + duplexer "^0.1.1" + minimist "^1.2.0" + through "^2.3.4" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0, supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +symbol-observable@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz" + integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== + +tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +tar-stream@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar@^6.1.11, tar@^6.1.2: + version "6.2.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz" + integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +terser-webpack-plugin@^5.3.10: + version "5.3.10" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz" + integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== + dependencies: + "@jridgewell/trace-mapping" "^0.3.20" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.1" + terser "^5.26.0" + +terser@5.29.1, terser@^5.26.0: + version "5.29.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.29.1.tgz" + integrity sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +through@^2.3.4: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + +tmp@0.2.3, tmp@^0.2.1, tmp@~0.2.1: + version "0.2.3" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz" + integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tree-kill@1.2.2, tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + +ts-api-utils@^1.0.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.2.1.tgz" + integrity sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA== + +ts-node@10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tsconfig-paths@^3.14.2: + version "3.14.2" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz" + integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tsconfig-paths@^4.1.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@2.6.2, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +tuf-js@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tuf-js/-/tuf-js-2.2.0.tgz" + integrity sha512-ZSDngmP1z6zw+FIkIBjvOp/II/mIub/O7Pp12j1WNsiCpg5R5wAc//i555bBQsE44O94btLt0xM/Zr2LQjwdCg== + dependencies: + "@tufjs/models" "2.0.0" + debug "^4.3.4" + make-fetch-happen "^13.0.0" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typed-array-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz" + integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-typed-array "^1.1.10" + +typed-array-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz" + integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz" + integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-length@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz" + integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + is-typed-array "^1.1.9" + +typed-assert@^1.0.8: + version "1.0.9" + resolved "https://registry.yarnpkg.com/typed-assert/-/typed-assert-1.0.9.tgz" + integrity sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg== + +typescript@^5.2.2: + version "5.4.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz" + integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ== + +ua-parser-js@^0.7.30: + version "0.7.37" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.37.tgz" + integrity sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA== + +ua-parser-js@^1.0.33: + version "1.0.37" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.37.tgz" + integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +undici@6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.7.1.tgz" + integrity sha512-+Wtb9bAQw6HYWzCnxrPTMVEV3Q1QjYanI0E4q02ehReMuquQdLTEFEYbfs7hcImVYKcQkWSwT6buEmSVIiDDtQ== + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz" + integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== + +unique-filename@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-3.0.0.tgz" + integrity sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g== + dependencies: + unique-slug "^4.0.0" + +unique-slug@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-4.0.0.tgz" + integrity sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ== + dependencies: + imurmurhash "^0.1.4" + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +validate-npm-package-license@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +validate-npm-package-name@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz" + integrity sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ== + dependencies: + builtins "^5.0.0" + +vary@^1, vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +vite@5.1.5: + version "5.1.5" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.1.5.tgz" + integrity sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q== + dependencies: + esbuild "^0.19.3" + postcss "^8.4.35" + rollup "^4.2.0" + optionalDependencies: + fsevents "~2.3.3" + +void-elements@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz" + integrity sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung== + +watchpack@2.4.0, watchpack@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz" + integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz" + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== + dependencies: + defaults "^1.0.3" + +webpack-dev-middleware@6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-6.1.1.tgz" + integrity sha512-y51HrHaFeeWir0YO4f0g+9GwZawuigzcAdRNon6jErXy/SqV/+O6eaVAzDqE6t3e3NpGeR5CS+cCDaTC+V3yEQ== + dependencies: + colorette "^2.0.10" + memfs "^3.4.12" + mime-types "^2.1.31" + range-parser "^1.2.1" + schema-utils "^4.0.0" + +webpack-dev-middleware@^5.3.1: + version "5.3.3" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz" + integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== + dependencies: + colorette "^2.0.10" + memfs "^3.4.3" + mime-types "^2.1.31" + range-parser "^1.2.1" + schema-utils "^4.0.0" + +webpack-dev-server@4.15.1: + version "4.15.1" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz" + integrity sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/serve-static" "^1.13.10" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.5.5" + ansi-html-community "^0.0.8" + bonjour-service "^1.0.11" + chokidar "^3.5.3" + colorette "^2.0.10" + compression "^1.7.4" + connect-history-api-fallback "^2.0.0" + default-gateway "^6.0.3" + express "^4.17.3" + graceful-fs "^4.2.6" + html-entities "^2.3.2" + http-proxy-middleware "^2.0.3" + ipaddr.js "^2.0.1" + launch-editor "^2.6.0" + open "^8.0.9" + p-retry "^4.5.0" + rimraf "^3.0.2" + schema-utils "^4.0.0" + selfsigned "^2.1.1" + serve-index "^1.9.1" + sockjs "^0.3.24" + spdy "^4.0.2" + webpack-dev-middleware "^5.3.1" + ws "^8.13.0" + +webpack-merge@5.10.0: + version "5.10.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz" + integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.0" + +webpack-sources@^3.0.0, webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack-subresource-integrity@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz" + integrity sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q== + dependencies: + typed-assert "^1.0.8" + +webpack@5.90.3: + version "5.90.3" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.90.3.tgz" + integrity sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^1.0.5" + "@webassemblyjs/ast" "^1.11.5" + "@webassemblyjs/wasm-edit" "^1.11.5" + "@webassemblyjs/wasm-parser" "^1.11.5" + acorn "^8.7.1" + acorn-import-assertions "^1.9.0" + browserslist "^4.21.10" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.15.0" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.9" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.2.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.0" + webpack-sources "^3.2.3" + +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-typed-array@^1.1.11, which-typed-array@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz" + integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.4" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + +which@^1.2.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +which@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/which/-/which-4.0.0.tgz" + integrity sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg== + dependencies: + isexe "^3.1.1" + +wildcard@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@^8.13.0: + version "8.14.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz" + integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== + +ws@~8.11.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz" + integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== + +xhr2@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.2.1.tgz" + integrity sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw== + +xmlhttprequest-ssl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz" + integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@21.1.1, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@17.1.1: + version "17.1.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.1.1.tgz" + integrity sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@17.7.2, yargs@^17.2.1, yargs@^17.3.1, yargs@^17.6.2, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yargs@^16.1.1: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yarn@^1.22.22: + version "1.22.22" + resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.22.tgz" + integrity sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yocto-queue@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz" + integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== + +zone.js@~0.14.0: + version "0.14.4" + resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.14.4.tgz" + integrity sha512-NtTUvIlNELez7Q1DzKVIFZBzNb646boQMgpATo9z3Ftuu/gWvzxCW7jdjcUDoRGxRikrhVHB/zLXh1hxeJawvw== + dependencies: + tslib "^2.3.0" diff --git a/web/install/launch.sh b/web/install/launch.sh new file mode 100755 index 0000000..d007e0a --- /dev/null +++ b/web/install/launch.sh @@ -0,0 +1,12 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +exec java -jar "$(ls *.jar | head -n1)" \ + --spring.config.additional-location=optional:file:/app/config/ diff --git a/web/pom.xml b/web/pom.xml new file mode 100644 index 0000000..ba435fa --- /dev/null +++ b/web/pom.xml @@ -0,0 +1,300 @@ + + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.2 + + + + org.mitre + web + 2.0.0 + + 17 + yarn + ${project.basedir}/client/node_modules/.bin/ng + false + 1.20.4 + + + + + + org.springframework.boot + spring-boot-starter-activemq + + + org.messaginghub + pooled-jms + + + + org.jsoup + jsoup + 1.17.2 + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.mysql + mysql-connector-j + + + + + + org.mitre.jet + jet + 1.0.0 + + + org.mitre + buffers + 1.0 + + + org.mitre + common + 1.0.0 + + + org.mitre + support + 1.0.0 + + + + + com.google.protobuf + protobuf-java + 3.25.3 + + + com.google.guava + guava + 33.1.0-jre + + + org.apache.commons + commons-collections4 + 4.5.0 + + + org.junit.vintage + junit-vintage-engine + test + + + org.testcontainers + mysql + test + + + net.sf.opencsv + opencsv + 2.3 + + + org.apache.httpcomponents + httpclient + 4.5.14 + + + com.google.code.gson + gson + 2.10.1 + + + com.itextpdf + itextpdf + 5.5.13.3 + + + com.github.ulisesbocchio + jasypt-spring-boot-starter + 3.0.5 + + + org.jfree + jfreechart + 1.5.4 + + + org.json + json + 20240303 + + + jakarta.xml.bind + jakarta.xml.bind-api + 4.0.2 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + + + + org.apache.maven.plugins + maven-deploy-plugin + + + default-deploy + deploy + + + + + org.apache.maven.plugins + maven-site-plugin + 4.0.0-M13 + + true + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + generate-sources + + + + + + + + run + + + + + + + + org.codehaus.mojo + jaxb2-maven-plugin + 3.1.0 + + + generate schema types + generate-sources + + xjc + + + + + + src/main/resources/xsd/dummy.xsd + src/main/resources/xsd/mismatchRequest.xsd + + org.mitre.iwp.mismatchrequest + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + target/generated-sources/jaxb + + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.2.0 + + + yarn-install + generate-sources + + ${project.basedir}/client + ${yarn} + + install + + ${build.node.skip} + + + exec + + + + ng-build + generate-sources + + ${project.basedir}/client + ${ng} + + build + iris-workstation + --output-path=${project.build.outputDirectory}/public + + ${build.node.skip} + + + exec + + + + + + + diff --git a/web/src/main/java/org/mitre/iwp/web/Application.java b/web/src/main/java/org/mitre/iwp/web/Application.java new file mode 100644 index 0000000..446a7a9 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/Application.java @@ -0,0 +1,43 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web; + +import org.jetbrains.annotations.NotNull; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties; + + +@SpringBootApplication +@EnableScheduling +@EnableEncryptableProperties +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(@NotNull CorsRegistry registry) { + registry.addMapping("/api/**").allowedOrigins("http://localhost:8080"); + registry.addMapping("/api/**").allowedOrigins("http://localhost:4200"); + } + }; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/IwpWebProperties.java b/web/src/main/java/org/mitre/iwp/web/IwpWebProperties.java new file mode 100644 index 0000000..e18de78 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/IwpWebProperties.java @@ -0,0 +1,609 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +import java.util.*; + +@Configuration +@PropertySource("classpath:application.properties") +@ConfigurationProperties(prefix = "iris") +public class IwpWebProperties { + public boolean getBiqtService() { + return biqtService; + } + public void setBiqtService(boolean biqtService) { + this.biqtService = biqtService; + } + + public String getBiqtQueueRequest() { + return biqtQueueRequest; + } + public void setBiqtQueueRequest(String biqtQueueRequest) { + this.biqtQueueRequest = biqtQueueRequest; + } + + public String getBiqtQueueResponse() { + return biqtQueueResponse; + } + public void setBiqtQueueResponse(String biqtQueueResponse) { + this.biqtQueueResponse = biqtQueueResponse; + } + + public boolean getTshepiiService() { + return tshepiiService; + } + public void setTshepiiService(boolean tshepiiService) { + this.tshepiiService = tshepiiService; + } + + public String getTshepiiQueueRequest() { + return tshepiiQueueRequest; + } + public void setTshepiiQueueRequest(String tshepiiQueueRequest) { + this.tshepiiQueueRequest = tshepiiQueueRequest; + } + + public String getTshepiiQueueResponse() { + return tshepiiQueueResponse; + } + public void setTshepiiQueueResponse(String tshepiiQueueResponse) { this.tshepiiQueueResponse = tshepiiQueueResponse; } + + // Dual PDM + public boolean getDualPDMService() { + return dualPDMService; + } + public void setDualPDMService(boolean dualPDMService) { + this.dualPDMService = dualPDMService; + } + + public String getDualPDMQueueRequest() { + return dualPDMQueueRequest; + } + public void setDualPDMQueueRequest(String dualPDMQueueRequest) { + this.dualPDMQueueRequest = dualPDMQueueRequest; + } + + public String getDualPDMQueueResponse() { + return dualPDMQueueResponse; + } + public void setDualPDMQueueResponse(String dualPDMQueueResponse) { + this.dualPDMQueueResponse = dualPDMQueueResponse; + } + + public boolean getAciiService() { return aciiService; } + public void setAciiService(boolean aciiService) { + this.aciiService = aciiService; + } + + public String getAciiQueueRequest() { return aciiQueueRequest; } + public void setAciiQueueRequest(String aciiQueueRequest) { + this.aciiQueueRequest = aciiQueueRequest; + } + + public String getAciiQueueResponse() { return aciiQueueResponse; } + public void setAciiQueueResponse(String aciiQueueResponse) { + this.aciiQueueResponse = aciiQueueResponse; + } + + public String getContactQueueResponse() { return contactQueueResponse; } + public void setContactQueueResponse(String contactQueueResponse) {this.contactQueueResponse = contactQueueResponse;} + + public boolean getIrisAnnotationService() { return irisAnnotationService; } + public void setIrisAnnotationService(boolean irisAnnotationService) { this.irisAnnotationService = irisAnnotationService; } + + public String getIrisAnnotationQueueRequest() { return irisAnnotationQueueRequest; } + public void setIrisAnnotationQueueRequest(String irisAnnotationQueueRequest) { this.irisAnnotationQueueRequest = irisAnnotationQueueRequest; } + + public String getIrisAnnotationQueueResponse() { return this.irisAnnotationQueueResponse; } + public void setIrisAnnotationQueueResponse(String irisAnnotationQueueResponse){this.irisAnnotationQueueResponse = irisAnnotationQueueResponse;} + + public boolean getPdmService() { return pdmService; } + public void setPdmService(boolean pdmService) { this.pdmService = pdmService; } + + public String getPdmQueueRequest() { return pdmQueueRequest; } + public void setPdmQueueRequest(String pdmQueueRequest) { this.pdmQueueRequest = pdmQueueRequest; } + + public String getPdmQueueResponse() { return pdmQueueResponse; } + public void setPdmQueueResponse(String pdmQueueResponse) { this.pdmQueueResponse = pdmQueueResponse; } + + public List getExcludedServices() { return this.excludedServices; } + public void setExcludedServices(List excludedServices){this.excludedServices = excludedServices;} + + public String getActiveMqAddress() { return activeMqAddress; } + public void setActiveMqAddress(String activeMqAddress) { this.activeMqAddress = activeMqAddress; } + + public String getIwpWebAddress() { return iwpWebAddress; } + public void setIwpWebAddress(String iwpWebAddress) { this.iwpWebAddress = iwpWebAddress; } + + + public String getTot() { + return tot; + } + + public void setTot(String tot) { + this.tot = tot; + } + + public String getPry() { + return pry; + } + + public void setPry(String pry) { + this.pry = pry; + } + + public String getDai() { + return dai; + } + + public void setDai(String dai) { + this.dai = dai; + } + + public String getOri() { + return ori; + } + + public void setOri(String ori) { + this.ori = ori; + } + + public String getTcn() { + return tcn; + } + + public void setTcn(String tcn) { + this.tcn = tcn; + } + + public String getTcr() { + return tcr; + } + + public void setTcr(String tcr) { + this.tcr = tcr; + } + + public String getNsr() { + return nsr; + } + + public void setNsr(String nsr) { + this.nsr = nsr; + } + + public String getNtr() { + return ntr; + } + + public void setNtr(String ntr) { + this.ntr = ntr; + } + + public String getDnm() { + return dnm; + } + + public void setDnm(String dnm) { + this.dnm = dnm; + } + + public String getDvn() { + return dvn; + } + + public void setDvn(String dvn) { + this.dvn = dvn; + } + + public String getApo() { + return apo; + } + + public void setApo(String apo) { + this.apo = apo; + } + + public String getApn() { + return apn; + } + + public void setApn(String apn) { + this.apn = apn; + } + + public String getApv() { + return apv; + } + + public void setApv(String apv) { + this.apv = apv; + } + + public String getAtn() { + return atn; + } + + public void setAtn(String atn) { + this.atn = atn; + } + + public String getCix() { + return cix; + } + + public void setCix(String cix) { + this.cix = cix; + } + + public String getNcr() { + return ncr; + } + + public void setNcr(String ncr) { + this.ncr = ncr; + } + + public String getCga() { + return cga; + } + + public void setCga(String cga) { + this.cga = cga; + } + + public String getProfileDai() { + return profileDai; + } + + public void setProfileDai(String profileDai) { + this.profileDai = profileDai; + } + + public String getProfileOri() { + return profileOri; + } + + public void setProfileOri(String profileOri) { + this.profileOri = profileOri; + } + + public String getProfileAttIndicator() { + return profileAttIndicator; + } + + public void setProfileAttIndicator(String profileAttIndicator) { + this.profileAttIndicator = profileAttIndicator; + } + + public String getProfileSrcAgency() { + return profileSrcAgency; + } + + public void setProfileSrcAgency(String profileSrcAgency) { + this.profileSrcAgency = profileSrcAgency; + } + + public Boolean getIsProfileReportExclude() { + return isProfileReportExclude; + } + + public void setIsProfileReportExclude(Boolean isProfileReportExclude) { + this.isProfileReportExclude = isProfileReportExclude; + } + + public String getProfileDpt() { + return profileDpt; + } + + public void setProfileDpt(String profileDpt) { + this.profileDpt = profileDpt; + } + + public String getProfileAdd() { + return profileAdd; + } + + public void setProfileAdd(String profileAdd) { + this.profileAdd = profileAdd; + } + + public Integer getProfileStrokeWidth() { + return profileStrokeWidth; + } + + public void setProfileStrokeWidth(Integer profileStrokeWidth) { + this.profileStrokeWidth = profileStrokeWidth; + } + + public String getProfileDrawColor() { + return profileDrawColor; + } + + public void setProfileDrawColor(String profileDrawColor) { + this.profileDrawColor = profileDrawColor; + } + + public String getProfileFillColor() { + return profileFillColor; + } + + public void setProfileFillColor(String profileFillColor) { + this.profileFillColor = profileFillColor; + } + + public Integer getProfileNumberHistory() { + return profileNumberHistory; + } + + public Map getAnnotations(){return annotations;} + public Map getAnnotationsColorHex(){return annotationsColorHex;} + + public void setAdjudicationValue(Map adjudicationValue){ + this.adjudicationValueLoaded.clear(); + for(Map.Entry entry : adjudicationValue.entrySet()){ + String[] adjudicationSettings = entry.getValue().split(","); + + if(adjudicationSettings.length == 2){ + this.adjudicationValueLoaded.put(adjudicationSettings[0], adjudicationSettings[1]); + } + } + } + + public Map getAdjudicationValue(){ + return this.adjudicationValueLoaded; + } + + public List getAdjudicationKeys() { + return new ArrayList<>(this.adjudicationValueLoaded.keySet()); + } + + public void setProfileNumberHistory(Integer profileNumberHistory) { + this.profileNumberHistory = profileNumberHistory; + } + + public String getAciiServiceName() { + return this.aciiServiceName; + } + + public void setAciiServiceName(String aciiServiceName){ + this.aciiServiceName = aciiServiceName; + } + + public String getBiqtServiceName(){ + return this.biqtServiceName; + } + + public void setBiqtServiceName(String biqtServiceName){ + this.biqtServiceName = biqtServiceName; + } + + public String getTshepiiServiceName() { + return this.tshepiiServiceName; + } + + public void setTshepiiServiceName(String tshepiiServiceName){ + this.tshepiiServiceName = tshepiiServiceName; + } + + // Dual PDM + public String getDualPDMServiceName() { + return this.dualPDMServiceName; + } + + public void setDualPDMServiceName(String dualPDMServiceName){ + this.dualPDMServiceName = dualPDMServiceName; + } + + public String getDualPDMServiceVersion() { + return dualPDMServiceVersion; + } + + public void setDualPDMServiceVersion(String dualPDMServiceVersion) { + this.dualPDMServiceVersion = dualPDMServiceVersion; + } + + public String getIrisAnnotationServiceName() { + return this.irisAnnotationServiceName; + } + + public void setIrisAnnotationServiceName(String irisAnnotationServiceName){ + this.irisAnnotationServiceName = irisAnnotationServiceName; + } + + public String getPdmServiceName() { + return this.pdmServiceName; + } + + public void setPdmServiceName(String pdmServiceName){ + this.pdmServiceName = pdmServiceName; + } + + public String getBiqtServiceVersion() { + return biqtServiceVersion; + } + + public String getAciiServiceVersion() { + return aciiServiceVersion; + } + + public String getTshepiiServiceVersion() { + return tshepiiServiceVersion; + } + + public String getIrisAnnotationServiceVersion() { + return irisAnnotationServiceVersion; + } + + public String getPdmServiceVersion() { + return pdmServiceVersion; + } + + public String getIwpBackendVersion() { + return iwpBackendVersion; + } + + public String getIwpFrontendVersion() { + return iwpFrontendVersion; + } + + public void setBiqtServiceVersion(String biqtServiceVersion) { + this.biqtServiceVersion = biqtServiceVersion; + } + + public void setAciiServiceVersion(String aciiServiceVersion) { + this.aciiServiceVersion = aciiServiceVersion; + } + + public void setTshepiiServiceVersion(String tshepiiServiceVersion) { + this.tshepiiServiceVersion = tshepiiServiceVersion; + } + + + public void setIrisAnnotationServiceVersion(String irisAnnotationServiceVersion) { + this.irisAnnotationServiceVersion = irisAnnotationServiceVersion; + } + + public void setPdmServiceVersion(String pdmServiceVersion) { + this.pdmServiceVersion = pdmServiceVersion; + } + + public void setIwpBackendVersion(String iwpBackendVersion) { + this.iwpBackendVersion = iwpBackendVersion; + } + + public void setIwpFrontendVersion(String iwpFrontendVersion) { + this.iwpFrontendVersion = iwpFrontendVersion; + } + + public String getActiveMqUserName() { + return this.activeMqUserName; + } + + public void setActiveMqUserName(String activeMqUserName){ + this.activeMqUserName = activeMqUserName; + } + + public String getActiveMqPassword() { + return this.activeMqPassword; + } + + public void setActiveMqPassword(String activeMqPassword){ + this.activeMqPassword = activeMqPassword; + } + + public String getActiveMqHttpAddress() { + return this.activeMqHttpAddress; + } + + public void setActiveMqHttpAddress(String activeMqHttpAddress){ + this.activeMqHttpAddress = activeMqHttpAddress; + } + + public int getMaxWidthImage() { + return maxWidthImage; + } + + public void setMaxWidthImage(int maxWidthImage) { + this.maxWidthImage = maxWidthImage; + } + + public int getMaxHeightImage() { + return maxHeightImage; + } + + public void setMaxHeightImage(int maxHeightImage) { + this.maxHeightImage = maxHeightImage; + } + + private String tot; + private String pry; + private String dai; + private String ori; + private String tcn; + private String tcr; + private String nsr; + private String ntr; + private String dnm; + private String dvn; + private String apo; + private String apn; + private String apv; + private String atn; + private String cix; + private String ncr; + private String cga; + private boolean biqtService; + private boolean tshepiiService; + private boolean dualPDMService; + private boolean aciiService; + private boolean irisAnnotationService; + private boolean pdmService; + private String biqtQueueRequest; + private String biqtQueueResponse; + private String aciiQueueRequest; + private String aciiQueueResponse; + private String tshepiiQueueRequest; + private String tshepiiQueueResponse; + private String dualPDMQueueRequest; + private String dualPDMQueueResponse; + private String contactQueueResponse; + private String irisAnnotationQueueRequest; + private String irisAnnotationQueueResponse; + private String pdmQueueRequest; + private String pdmQueueResponse; + private String aciiServiceName; + private String biqtServiceName; + private String tshepiiServiceName; + private String dualPDMServiceName; + private String irisAnnotationServiceName; + private String pdmServiceName; + private List excludedServices = new ArrayList<>(); + private String activeMqAddress; + private String iwpWebAddress; + private String activeMqUserName; + private String activeMqPassword; + private String activeMqHttpAddress; + + // Default Profiles + private String profileDai; + private String profileOri; + private String profileAttIndicator; + private String profileSrcAgency; + private Boolean isProfileReportExclude; + private String profileDpt; + private String profileAdd; + private int profileStrokeWidth; + private String profileDrawColor; + private String profileFillColor; + private int profileNumberHistory; + + private String biqtServiceVersion; + private String aciiServiceVersion; + private String tshepiiServiceVersion; + private String dualPDMServiceVersion; + private String irisAnnotationServiceVersion; + private String pdmServiceVersion; + private String iwpBackendVersion; + private String iwpFrontendVersion; + + private int maxWidthImage; + private int maxHeightImage; + + private final Map annotations = new HashMap<>(); + private final Map annotationsColorHex = new HashMap<>(); + private Map adjudicationValueLoaded = new LinkedHashMap<>(); + +} diff --git a/web/src/main/java/org/mitre/iwp/web/KeyValueStore.java b/web/src/main/java/org/mitre/iwp/web/KeyValueStore.java new file mode 100644 index 0000000..2c0aae1 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/KeyValueStore.java @@ -0,0 +1,58 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web; + +import org.apache.commons.lang3.StringUtils; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +public class KeyValueStore { + private static final Map data = new HashMap<>(); + + public static Integer getInteger(String key) { + return (Integer)(data.get(key)); + } + + public static Double getDouble(String key) { + return (Double)(data.get(key)); + } + + public static Object get(String key) { + return data.get(key); + } + + public static void put(Object value, String... keys) { + if (keys.length > 0) { + data.put(StringUtils.join(keys, ":"), value); + } + } + + public static void purge() { + data.clear(); + } + + public static void delete(String key) { + data.remove(key); + } + + public static void deletePattern(String regex) { + Set keys = data.keySet(); + Pattern pattern = Pattern.compile(regex); + for(String key : keys) { + if(pattern.matcher(key).matches()) { + data.remove(key); + } + } + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/data/AnnotationInformationField.java b/web/src/main/java/org/mitre/iwp/web/data/AnnotationInformationField.java new file mode 100644 index 0000000..a53bd4f --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/AnnotationInformationField.java @@ -0,0 +1,93 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class AnnotationInformationField { + + private static final Logger log = LoggerFactory.getLogger(AnnotationInformationField.class); + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private Timestamp greenwichMeanTime; + public Timestamp getGreenwichMeanTime() { return this.greenwichMeanTime; } + + private String processingAlgorithmNameVersion; + public String getProcessingAlgorithmNameVersion() { return this.processingAlgorithmNameVersion; } + + private String algorithmOwner; + public String getAlgorithmOwner() { return this.algorithmOwner; } + + private String processDescription; + public String getProcessDescription() { return this.processDescription; } + + public AnnotationInformationField() { } + public AnnotationInformationField(long id){ + this.id = id; + } + + public String toString() { + return String.format("%s#", + this.getClass().getSimpleName(), id, + this.greenwichMeanTime, + this.processingAlgorithmNameVersion, + this.algorithmOwner, + this.processDescription); + } + + public static AnnotationInformationField buildFromEbtsOccurence(Occurrence occurrence) { + AnnotationInformationField aif = new AnnotationInformationField(); + + if (occurrence == null) { + log.debug("BIL occurrence is null."); + } else if (occurrence.getSubFields().size() != 4) { + log.debug("ANN field contains an unexpected number of subfields ({}) and will not be parsed.", occurrence.getSubFields().size()); + } else { + try { + aif.greenwichMeanTime = Timestamp.valueOf(EbtsConversionHelper.getStringFromOccurrence(occurrence, 0)); + } catch(Exception e) { //this generic but you can control another types of exception + log.error("Error parsing Greenwich Mean Time '{}' to Timestamp", EbtsConversionHelper.getStringFromOccurrence(occurrence, 0)); + } + aif.processingAlgorithmNameVersion = EbtsConversionHelper.getStringFromOccurrence(occurrence, 1); + aif.algorithmOwner = EbtsConversionHelper.getStringFromOccurrence(occurrence, 2); + aif.processDescription = EbtsConversionHelper.getStringFromOccurrence(occurrence, 3); + } + + return aif; + } + + public static Occurrence buildEbtsOccurrence(AnnotationInformationField apsf) { + Occurrence occurrence = new Occurrence(); + List subFields = new ArrayList<>(); + subFields.add(EbtsConversionHelper.getSubFieldFromString(apsf.getGreenwichMeanTime().toString())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(apsf.getProcessingAlgorithmNameVersion())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(apsf.getAlgorithmOwner())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(apsf.getProcessDescription())); + occurrence.setSubfields(subFields); + return occurrence; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/data/ApplicationProfileSpecificationsField.java b/web/src/main/java/org/mitre/iwp/web/data/ApplicationProfileSpecificationsField.java new file mode 100644 index 0000000..0c0e516 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/ApplicationProfileSpecificationsField.java @@ -0,0 +1,96 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class ApplicationProfileSpecificationsField { + + private static final Logger log = LoggerFactory.getLogger(ApplicationProfileSpecificationsField.class); + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String applicationProfileOrganization; + public String getApplicationProfileOrganization() { return applicationProfileOrganization; } + + public void setApplicationProfileOrganization(String applicationProfileOrganization) { + this.applicationProfileOrganization = applicationProfileOrganization; + } + + private String applicationProfileName; + public String getApplicationProfileName() { return applicationProfileName; } + + public void setApplicationProfileName(String applicationProfileName) { + this.applicationProfileName = applicationProfileName; + } + + private String applicationProfileVersionNumber; + public String getApplicationProfileVersionNumber() { return applicationProfileVersionNumber; } + + public void setApplicationProfileVersionNumber(String applicationProfileVersionNumber) { + this.applicationProfileVersionNumber = applicationProfileVersionNumber; + } + + public ApplicationProfileSpecificationsField() { + // Empty constructor to satisfy JPA requirement + } + + public String toString() { + return String.format("%s#", + this.getClass().getSimpleName(), id, applicationProfileOrganization, applicationProfileName, applicationProfileVersionNumber); + } + + public static ApplicationProfileSpecificationsField buildFromEbtsField(org.mitre.jet.ebts.field.Field field) { + ApplicationProfileSpecificationsField apsf = new ApplicationProfileSpecificationsField(); + + if (field == null) { + log.debug("APS field not provided"); + } else if (field.getOccurrences().size() != 1) { + log.debug("APS field contains an unexpected number of occurrences ({}) and will not be parsed.", field.getOccurrences().size()); + } else { + Occurrence occurrence = field.getOccurrences().get(0); + + if (occurrence.getSubFields().size() != 3) { + log.debug("APS field contains an unexpected number of subfields ({}) and will not be parsed.", occurrence.getSubFields().size()); + } else { + apsf.applicationProfileOrganization = EbtsConversionHelper.getStringFromOccurrence(occurrence, 0); + apsf.applicationProfileName = EbtsConversionHelper.getStringFromOccurrence(occurrence, 1); + apsf.applicationProfileVersionNumber = EbtsConversionHelper.getStringFromOccurrence(occurrence, 2); + } + } + + return apsf; + } + + public static Occurrence buildEbtsOccurrence(ApplicationProfileSpecificationsField apsf) { + Occurrence occurrence = new Occurrence(); + List subFields = new ArrayList<>(); + subFields.add(EbtsConversionHelper.getSubFieldFromString(apsf.getApplicationProfileOrganization())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(apsf.getApplicationProfileName())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(apsf.getApplicationProfileVersionNumber())); + occurrence.setSubfields(subFields); + return occurrence; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/data/BiometricImageDescriptionField.java b/web/src/main/java/org/mitre/iwp/web/data/BiometricImageDescriptionField.java new file mode 100644 index 0000000..b46c291 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/BiometricImageDescriptionField.java @@ -0,0 +1,103 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class BiometricImageDescriptionField { + private static final Logger log = LoggerFactory.getLogger(BiometricImageDescriptionField.class); + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String subjectIdentifier; + public String getSubjectIdentifier() { return subjectIdentifier; } + + private int imageType; + public int getImageType() { return imageType; } + + private String biometricSetIdentifier; + public String getBiometricSetIdentifier() { return biometricSetIdentifier; } + + private int fingerNumberRequested; + public int getFingerNumberRequested() { return fingerNumberRequested; } + + private String printPositionDescriptors; + public String getPrintPositionDescriptors() { return printPositionDescriptors; } + + private String subjectPose; + public String getSubjectPose() { return subjectPose; } + + private String ncicSmtCode; + public String getNcicSmtCode() { return ncicSmtCode; } + + private int eyeLabel; + public int getEyeLabel() { return eyeLabel; } + + public BiometricImageDescriptionField() { + // Empty constructor to satisfy JPA requirement + } + + public String toString() { + return String.format("%s#", + this.getClass().getSimpleName(), + id); + } + + public static BiometricImageDescriptionField buildFromEbtsOccurence(Occurrence occurrence){ + BiometricImageDescriptionField bidf = new BiometricImageDescriptionField(); + + if (occurrence == null) { + log.debug("BID occurrence is null."); + } else if (occurrence.getSubFields().size() != 8) { + log.debug("BID field contains an unexpected number of subfields ({}) and will not be parsed.", occurrence.getSubFields().size()); + } else { + bidf.subjectIdentifier = EbtsConversionHelper.getStringFromOccurrence(occurrence, 0); + bidf.imageType = EbtsConversionHelper.getIntFromOccurrence(occurrence, 1, Integer.MAX_VALUE); + bidf.biometricSetIdentifier = EbtsConversionHelper.getStringFromOccurrence(occurrence, 2); + bidf.fingerNumberRequested = EbtsConversionHelper.getIntFromOccurrence(occurrence, 3, Integer.MAX_VALUE); + bidf.printPositionDescriptors = EbtsConversionHelper.getStringFromOccurrence(occurrence, 4); + bidf.subjectPose = EbtsConversionHelper.getStringFromOccurrence(occurrence, 5); + bidf.ncicSmtCode = EbtsConversionHelper.getStringFromOccurrence(occurrence, 6); + bidf.eyeLabel = EbtsConversionHelper.getIntFromOccurrence(occurrence, 7, Integer.MAX_VALUE); + } + + return bidf; + } + + public static Occurrence buildEbtsOccurrence(BiometricImageDescriptionField bid) { + Occurrence occurrence = new Occurrence(); + List subFields = new ArrayList<>(); + subFields.add(EbtsConversionHelper.getSubFieldFromString(bid.getSubjectIdentifier())); + subFields.add(EbtsConversionHelper.getSubFieldFromInt(bid.getImageType())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(bid.getBiometricSetIdentifier())); + subFields.add(EbtsConversionHelper.getSubFieldFromInt(bid.getFingerNumberRequested())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(bid.getPrintPositionDescriptors())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(bid.getSubjectPose())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(bid.getNcicSmtCode())); + subFields.add(EbtsConversionHelper.getSubFieldFromInt(bid.getEyeLabel())); + occurrence.setSubfields(subFields); + return occurrence; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/data/BiometricImageEnrollmentField.java b/web/src/main/java/org/mitre/iwp/web/data/BiometricImageEnrollmentField.java new file mode 100644 index 0000000..0d6c677 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/BiometricImageEnrollmentField.java @@ -0,0 +1,83 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class BiometricImageEnrollmentField { + + private static final Logger log = LoggerFactory.getLogger(BiometricImageEnrollmentField.class); + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String biometricSetIdentifier; + public String getBiometricSetIdentifier() { return biometricSetIdentifier; } + + private int imageType; + public int getImageType() { return imageType; } + + private String subjectPose; + public String getSubjectPose() { return subjectPose; } + + private String scarsMarksTattoos; + public String getScarsMarksTattoos() { return scarsMarksTattoos; } + + public BiometricImageEnrollmentField() { + // Empty constructor to satisfy JPA requirement + } + + public String toString() { + return String.format("%s#", + this.getClass().getSimpleName(), id, biometricSetIdentifier, imageType, subjectPose, scarsMarksTattoos); + } + + public static BiometricImageEnrollmentField buildFromEbtsOccurence(Occurrence occurrence) { + BiometricImageEnrollmentField bief = new BiometricImageEnrollmentField(); + + if (occurrence == null) { + log.debug("BIE occurrence is null."); + } else if (occurrence.getSubFields().size() != 4) { + log.debug("BIE field contains an unexpected number of subfields ({}) and will not be parsed.", occurrence.getSubFields().size()); + } else { + bief.biometricSetIdentifier = EbtsConversionHelper.getStringFromOccurrence(occurrence, 0); + bief.imageType = EbtsConversionHelper.getIntFromOccurrence(occurrence, 1, Integer.MAX_VALUE); + bief.subjectPose = EbtsConversionHelper.getStringFromOccurrence(occurrence, 2); + bief.scarsMarksTattoos = EbtsConversionHelper.getStringFromOccurrence(occurrence, 3); + } + + return bief; + } + + public static Occurrence buildEbtsOccurrence(BiometricImageEnrollmentField bie) { + Occurrence occurrence = new Occurrence(); + List subFields = new ArrayList<>(); + subFields.add(EbtsConversionHelper.getSubFieldFromString(bie.getBiometricSetIdentifier())); + subFields.add(EbtsConversionHelper.getSubFieldFromInt(bie.getImageType())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(bie.getSubjectPose())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(bie.getScarsMarksTattoos())); + occurrence.setSubfields(subFields); + return occurrence; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/data/BiometricImageListField.java b/web/src/main/java/org/mitre/iwp/web/data/BiometricImageListField.java new file mode 100644 index 0000000..2e77c11 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/BiometricImageListField.java @@ -0,0 +1,91 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class BiometricImageListField { + + private static final Logger log = LoggerFactory.getLogger(BiometricImageListField.class); + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String subjectIdentifier; + public String getSubjectIdentifier() { return this.subjectIdentifier; } + + private String biometricSetIdentifier; + public String getBiometricSetIdentifier() { return this.biometricSetIdentifier; } + + private Timestamp biometricCaptureDate; + public Timestamp getBiometricCaptureDate() { return this.biometricCaptureDate; } + + private int imageType; + public int getImageType() { return this.imageType; } + + public BiometricImageListField() { + // Empty constructor to satisfy JPA requirement + } + + public String toString() { + return String.format("%s#", + this.getClass().getSimpleName(), id, subjectIdentifier, biometricSetIdentifier, biometricCaptureDate, imageType); + } + + public static BiometricImageListField buildFromEbtsOccurence(Occurrence occurrence) { + BiometricImageListField bilf = new BiometricImageListField(); + + if (occurrence == null) { + log.debug("BIL occurrence is null."); + } else if (occurrence.getSubFields().size() != 4) { + log.debug("BIL field contains an unexpected number of subfields ({}) and will not be parsed.", occurrence.getSubFields().size()); + + } else { + bilf.subjectIdentifier = EbtsConversionHelper.getStringFromOccurrence(occurrence, 0); + bilf.biometricSetIdentifier = EbtsConversionHelper.getStringFromOccurrence(occurrence, 1); + try { + bilf.biometricCaptureDate = Timestamp.valueOf(EbtsConversionHelper.getStringFromOccurrence(occurrence, 2)); + } catch(Exception e) { //this generic but you can control another types of exception + log.error("Error parsing Biometric Capture Date '{}' to Timestamp", EbtsConversionHelper.getStringFromOccurrence(occurrence, 2)); + } + + bilf.imageType = EbtsConversionHelper.getIntFromOccurrence(occurrence, 3, Integer.MAX_VALUE); + } + + return bilf; + } + + public static Occurrence buildEbtsOccurrence(BiometricImageListField bil) { + Occurrence occurrence = new Occurrence(); + List subFields = new ArrayList<>(); + subFields.add(EbtsConversionHelper.getSubFieldFromString(bil.getSubjectIdentifier())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(bil.getBiometricSetIdentifier())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(bil.getBiometricCaptureDate().toString())); + subFields.add(EbtsConversionHelper.getSubFieldFromInt(bil.getImageType())); + occurrence.setSubfields(subFields); + + return occurrence; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/data/CandidateInvesitgativeListField.java b/web/src/main/java/org/mitre/iwp/web/data/CandidateInvesitgativeListField.java new file mode 100644 index 0000000..16b9838 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/CandidateInvesitgativeListField.java @@ -0,0 +1,163 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class CandidateInvesitgativeListField { + + private static final Logger log = LoggerFactory.getLogger(CandidateInvesitgativeListField.class); + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String subjectIdentifier; + public String getSubjectIdentifier() { + return this.subjectIdentifier; + } + + private String masterName; + public String getMasterName() { + return this.masterName; + } + + private String biometricSetIdentifier; + public String getBiometricSetIdentifier() { + return this.biometricSetIdentifier; + } + + private int imageType; + public int getImageType() { + return this.imageType; + } + + private int frictionRidgeGeneralizedPosition; + public int getFrictionRidgeGeneralizedPosition() { + return this.frictionRidgeGeneralizedPosition; + } + + private String printPositionDescriptor; + public String getPrintPositionDescriptor() { + return this.printPositionDescriptor; + } + + private int matchScore; + public int getMatchScore() { + return this.matchScore; + } + + private int biometricImageAvailable; + public int getBiometricImageAvailable() { + return this.biometricImageAvailable; + } + + private int nameOfDesignatedRepository; + public int getNameOfDesignatedRepository() { + return this.nameOfDesignatedRepository; + } + + private String informationDesignationCharacter; + public String getInformationDesignationCharacter() { + return this.informationDesignationCharacter; + } + + private String noteField; + public String getNoteField() { + return this.noteField; + } + + private String subjectPose; + public String getSubjectPose() { + return this.subjectPose; + } + + private String ncicSmtCode; + public String getNcicSmtCode() { + return ncicSmtCode; + } + + private int eyeLabel; + public int getEyeLabel() { + return this.eyeLabel; + } + + public CandidateInvesitgativeListField() { + // Empty constructor to satisfy JPA requirement + } + + public String toString() { + return String.format("%s#", + this.getClass().getSimpleName(), + id); + } + + public static CandidateInvesitgativeListField buildFromEbtsOccurence(Occurrence occurrence) { + CandidateInvesitgativeListField cilf = new CandidateInvesitgativeListField(); + + if (occurrence == null) { + log.debug("CNL occurrence is null."); + } else if (occurrence.getSubFields().size() != 14) { + log.debug("CNL field contains an unexpected number of subfields ({}) and will not be parsed.", occurrence.getSubFields().size()); + } else { + cilf.subjectIdentifier = EbtsConversionHelper.getStringFromOccurrence(occurrence, 0); + cilf.masterName = EbtsConversionHelper.getStringFromOccurrence(occurrence, 1); + cilf.biometricSetIdentifier = EbtsConversionHelper.getStringFromOccurrence(occurrence, 2); + cilf.imageType = EbtsConversionHelper.getIntFromOccurrence(occurrence, 3, Integer.MAX_VALUE); + cilf.frictionRidgeGeneralizedPosition = EbtsConversionHelper.getIntFromOccurrence(occurrence, 4, Integer.MAX_VALUE); + cilf.printPositionDescriptor = EbtsConversionHelper.getStringFromOccurrence(occurrence, 5); + cilf.matchScore = EbtsConversionHelper.getIntFromOccurrence(occurrence, 6, Integer.MAX_VALUE); + cilf.biometricImageAvailable = EbtsConversionHelper.getIntFromOccurrence(occurrence, 7, Integer.MAX_VALUE); + cilf.nameOfDesignatedRepository = EbtsConversionHelper.getIntFromOccurrence(occurrence, 8, Integer.MAX_VALUE); + cilf.informationDesignationCharacter = EbtsConversionHelper.getStringFromOccurrence(occurrence, 9); + cilf.noteField = EbtsConversionHelper.getStringFromOccurrence(occurrence, 10); + cilf.subjectPose = EbtsConversionHelper.getStringFromOccurrence(occurrence, 11); + cilf.ncicSmtCode = EbtsConversionHelper.getStringFromOccurrence(occurrence, 12); + cilf.eyeLabel = EbtsConversionHelper.getIntFromOccurrence(occurrence, 13, Integer.MAX_VALUE); + } + + return cilf; + } + + public static Occurrence buildEbtsOccurrence(CandidateInvesitgativeListField cil) { + Occurrence occurrence = new Occurrence(); + List subFields = new ArrayList<>(); + subFields.add(EbtsConversionHelper.getSubFieldFromString(cil.getSubjectIdentifier())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(cil.getMasterName())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(cil.getBiometricSetIdentifier())); + subFields.add(EbtsConversionHelper.getSubFieldFromInt(cil.getImageType())); + subFields.add(EbtsConversionHelper.getSubFieldFromInt(cil.getFrictionRidgeGeneralizedPosition())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(cil.getPrintPositionDescriptor())); + subFields.add(EbtsConversionHelper.getSubFieldFromInt(cil.getMatchScore())); + subFields.add(EbtsConversionHelper.getSubFieldFromInt(cil.getBiometricImageAvailable())); + subFields.add(EbtsConversionHelper.getSubFieldFromInt(cil.getNameOfDesignatedRepository())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(cil.getInformationDesignationCharacter())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(cil.getNoteField())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(cil.getSubjectPose())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(cil.getNcicSmtCode())); + subFields.add(EbtsConversionHelper.getSubFieldFromInt(cil.getEyeLabel())); + occurrence.setSubfields(subFields); + + return occurrence; + } +} \ No newline at end of file diff --git a/web/src/main/java/org/mitre/iwp/web/data/ContributorCaseIdentifierNumberField.java b/web/src/main/java/org/mitre/iwp/web/data/ContributorCaseIdentifierNumberField.java new file mode 100644 index 0000000..6c6c68f --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/ContributorCaseIdentifierNumberField.java @@ -0,0 +1,84 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class ContributorCaseIdentifierNumberField { + + private static final Logger log = LoggerFactory.getLogger(ContributorCaseIdentifierNumberField.class); + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String contributorCasePrefix; + public String getContributorCasePrefix() { + return this.contributorCasePrefix; + } + public void setContributorCasePrefix(String contributorCasePrefix) { + this.contributorCasePrefix = contributorCasePrefix; + } + + private String contributorCaseId; + public String getContributorCaseId() { + return this.contributorCaseId; + } + public void setContributorCaseId(String contributorCaseId) { + this.contributorCaseId = contributorCaseId; + } + + public ContributorCaseIdentifierNumberField() { + // Empty constructor to satisfy JPA requirement + } + + public String toString() { + return String.format("%s#", + this.getClass().getSimpleName(), id, contributorCasePrefix, contributorCaseId); + } + + public static ContributorCaseIdentifierNumberField buildFromEbtsOccurence(Occurrence occurrence) { + ContributorCaseIdentifierNumberField ccinf = new ContributorCaseIdentifierNumberField(); + + if (occurrence == null) { + log.debug("CIN occurrence is null."); + } else if (occurrence.getSubFields().size() != 2) { + log.debug("CIN field contains an unexpected number of subfields ({}) and will not be parsed.", occurrence.getSubFields().size()); + } else { + ccinf.contributorCasePrefix = EbtsConversionHelper.getStringFromOccurrence(occurrence, 0); + ccinf.contributorCaseId = EbtsConversionHelper.getStringFromOccurrence(occurrence, 1); + } + + return ccinf; + } + + public static Occurrence buildEbtsOccurrence(ContributorCaseIdentifierNumberField cin) { + Occurrence occurrence = new Occurrence(); + List subFields = new ArrayList<>(); + subFields.add(EbtsConversionHelper.getSubFieldFromString(cin.getContributorCasePrefix())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(cin.getContributorCaseId())); + occurrence.setSubfields(subFields); + + return occurrence; + } +} \ No newline at end of file diff --git a/web/src/main/java/org/mitre/iwp/web/data/DomainNameField.java b/web/src/main/java/org/mitre/iwp/web/data/DomainNameField.java new file mode 100644 index 0000000..a2049e1 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/DomainNameField.java @@ -0,0 +1,86 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class DomainNameField { + + private static final Logger log = LoggerFactory.getLogger(DomainNameField.class); + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String domainName; + public String getDomainName() { return this.domainName; } + public void setDomainName(String domainName) { + this.domainName = domainName; + } + + private String domainVersionNumber; + public String getDomainVersionNumber() { return this.domainVersionNumber; } + public void setDomainVersionNumber(String domainVersionNumber) { + this.domainVersionNumber = domainVersionNumber; + } + + public DomainNameField() { + // Empty constructor to satisfy JPA requirement + } + + public String toString() { + return String.format("%s#", + this.getClass().getSimpleName(), id, domainName, domainVersionNumber); + } + + public static DomainNameField buildFromEbtsField(org.mitre.jet.ebts.field.Field field) { + DomainNameField dnf = new DomainNameField(); + + if (field == null) { + log.debug("DOM field not provided"); + } else if (field.getOccurrences().size() != 1) { + log.debug("DOM field contains an unexpected number of occurrences ({}) and will not be parsed.", field.getOccurrences().size()); + } else { + Occurrence occurrence = field.getOccurrences().get(0); + + if (occurrence.getSubFields().size() != 2) { + log.debug("DOM field contains an unexpected number of subfields ({}) and will not be parsed.", occurrence.getSubFields().size()); + } else { + dnf.domainName = EbtsConversionHelper.getStringFromOccurrence(occurrence, 0); + dnf.domainVersionNumber = EbtsConversionHelper.getStringFromOccurrence(occurrence, 1); + } + } + + return dnf; + } + + public static Occurrence buildEbtsOccurrence(DomainNameField dnf) { + Occurrence occurrence = new Occurrence(); + List subFields = new ArrayList<>(); + subFields.add(EbtsConversionHelper.getSubFieldFromString(dnf.getDomainName())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(dnf.getDomainVersionNumber())); + occurrence.setSubfields(subFields); + + return occurrence; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/data/EbtsConversionHelper.java b/web/src/main/java/org/mitre/iwp/web/data/EbtsConversionHelper.java new file mode 100644 index 0000000..2801476 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/EbtsConversionHelper.java @@ -0,0 +1,236 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.apache.commons.lang3.StringUtils; +import org.mitre.jet.ebts.field.Field; +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +public class EbtsConversionHelper { + private static final Logger log = LoggerFactory.getLogger(EbtsConversionHelper.class); + /** + * + * @param field a jet Ebts Field object with desired Integer data + * @return Returns the integer value input Field parameter. If the Field object is null, method returns 0 + */ + public static int getIntFromField(Field field, Integer defaultVal) { + if(field != null) { + try { + if(StringUtils.isEmpty(field.toString())){ + return defaultVal; + } + + return Integer.parseInt(field.toString()); + } catch (Exception e) { + log.warn("Failed to parse field {} as an integer", field); + } + } + + return defaultVal; + } + + /** + * + * @param field a jet Ebts Field object with desired String data + * @return Returns the String value input Field parameter. If the Field object is null, method returns an empty String + */ + public static String getStringFromField(Field field) { + if(field != null) { + try { + return field.toString(); + } catch (Exception e) { + log.warn("Failed to get string value from field"); + } + } + + return ""; + } + + /** + * + * @param occurrence a jet Ebts Occurence object with desired String data + * @param i the index of the desired String data within the Occurence Subfields + * @return Returns the string value from the ith subfield within the provided Occurrence object. + * If the location does not contain an ith element, then an empty String is returned + */ + public static String getStringFromOccurrence(Occurrence occurrence, int i) { + if(occurrence != null){ + try { + if(occurrence.getSubFields().size() > i) { + return occurrence.getSubFields().get(i).toString(); + } + }catch(Exception e) { + log.warn("Failed to get string value from field"); + } + } + + return ""; + } + + /** + * + * @param occurrence a jet Ebts Occurence object with desired Integer data + * @param i the index of the desired Integer data within the Occurence Subfields + * @return Returns the Integer value from the ith subfield within the provided Occurrence object. If the location does not contain an Int, or the occurrence subfields do + * not contain an ith element, then 0 is returned + */ + public static int getIntFromOccurrence(Occurrence occurrence, int i, Integer defaultVal) { + if(occurrence != null){ + try { + if(occurrence.getSubFields().size() > i){ + String temp = occurrence.getSubFields().get(i).toString(); + + if(StringUtils.isEmpty(temp)){ + return defaultVal; + } + + return Integer.parseInt(temp); + } + }catch(Exception e) { + log.warn("Failed to parse occurrence {} location {} as an integer", occurrence, i); + } + } + + return defaultVal; + } + + /** + * @param data String value to be set as data element in the SubField object + * @return Returns an Ebts SubField object containing the input data string + */ + public static SubField getSubFieldFromString(String data) { + SubField field = new SubField(); + field.setData(data); + return field; + } + + /** + * @param data Int value to be set as data element in the SubField object. + * @return Returns an Ebts SubField object containing the input data string. If input int is Max_Integer, + * SubField data element will be set to "" + */ + public static SubField getSubFieldFromInt(int data) { + SubField field = new SubField(); + + if (data == Integer.MAX_VALUE) { + field.setData(""); + } else { + field.setData(Integer.toString(data)); + } + + return field; + } + + /** + * @param dataArgs array of Strings to be set as data elements in Occurrence SubFields + * @return Returns an Ebts Occurrence object containing the input data strings. + */ + public static Occurrence getOccurrenceFromString(String... dataArgs) { + Occurrence occurrence = new Occurrence(); + List subList = new ArrayList<>(); + for (String data : dataArgs) { + + subList.add(getSubFieldFromString(data)); + } + + occurrence.setSubfields(subList); + return occurrence; + } + + /** + * @param dataArgs array of ints to be set as data elements in Occurrence SubFields + * @return Returns an Ebts Occurrence object containing the input data ints. If input ints are Max_Integer, + * SubField data element will be set to "" + */ + public static Occurrence getOccurrenceFromInt(int... dataArgs) { + Occurrence occurrence = new Occurrence(); + List subList = new ArrayList<>(); + for (int data : dataArgs) { + + subList.add(getSubFieldFromInt(data)); + } + + occurrence.setSubfields(subList); + return occurrence; + } + + /** + * @param dataArgs array of Strings to be set as data elements in Field/Occurrence SubFields + * @return an Ebts Field object with 1 Occurrence and a SubField per dataArg element passed in + */ + public static Field getFieldFromString(String... dataArgs) { + Field field = new Field(); + Occurrence occurrence = new Occurrence(); + List subList = new ArrayList<>(); + List occList = new ArrayList<>(); + + for (String data : dataArgs) { + subList.add(getSubFieldFromString(data)); + } + + occurrence.setSubfields(subList); + occList.add(occurrence); + + field.setOccurrences(occList); + + return field; + } + + /** + * @param dataArgs array of Integers to be set as data elements in Field/Occurrence SubFields + * @return an Ebts Field object with 1 Occurrence and a SubField per dataArg element passed in. If input Integers are Max_Integer, + * SubField data element will be set to "" + */ + public static Field getFieldFromInt(Integer... dataArgs) { + Field field = new Field(); + Occurrence occurrence = new Occurrence(); + List subList = new ArrayList<>(); + List occList = new ArrayList<>(); + + for (int data : dataArgs) { + subList.add(getSubFieldFromInt(data)); + } + + occurrence.setSubfields(subList); + occList.add(occurrence); + + field.setOccurrences(occList); + + return field; + } + + /** + * @return returns and Empty Field object to be populated + */ + public static Field getEmptyField() { + Field field = new Field(); + List occurrenceList = new ArrayList<>(); + field.setOccurrences(occurrenceList); + return field; + } + + /** + * + * @param occurenceList array of Occurrences + * @return an Ebts Field object populated with input Occurrences + */ + public static Field getFieldFromOccurrences(List occurenceList) { + Field field = new Field(); + field.setOccurrences(occurenceList); + return field; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/data/EbtsImage.java b/web/src/main/java/org/mitre/iwp/web/data/EbtsImage.java new file mode 100644 index 0000000..d2fd2ea --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/EbtsImage.java @@ -0,0 +1,126 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.mitre.iwp.common.ImageUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.imageio.ImageIO; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; + +@Entity +public class EbtsImage { + private static final Logger logger = LoggerFactory.getLogger(EbtsImage.class); + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String originalImageType; + public String getOriginalImageType() { return this.originalImageType; } + public void setOriginalImageType(String type) { this.originalImageType = type; } + + @Column(name = "image_hash", unique = true, nullable = false) + private String imageHash; + public String getImageHash() { return this.imageHash; } + public void setImageHash( String hash ) { this.imageHash = hash; } + + @Lob + @Column(columnDefinition="LONGBLOB") + private byte[] tshepiiResponse; + public byte[] getTshepiiResponse() { return this.tshepiiResponse; } + public void setTshepiiResponse(byte[] response){ this.tshepiiResponse = response; } + + private String contactClassifierResponse; + public String getContactClassifierResponse() { return this.contactClassifierResponse; } + public void setContactClassifierResponse(String response) { this.contactClassifierResponse = response; } + + private String path; + public String getPath() { return path; } + public void setPath( String p ) { this.path = p; } + + public EbtsImage() { + // Empty constructor to satisfy JPA requirement + } + + public String toString() { + return String.format("%s#", + this.getClass().getSimpleName(), + this.originalImageType, + this.path); + } + + public static byte[] buildThumbnail(EbtsImage image) throws IOException { + ByteArrayInputStream bais = new ByteArrayInputStream(EbtsImage.getImageBytesPng(image)); + BufferedImage sourceImage = ImageIO.read(bais); + AffineTransform tx = new AffineTransform(); + tx.scale(.25, .25); + + AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR); + sourceImage = op.filter(sourceImage, null); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ImageIO.write(sourceImage, "png", out); + return out.toByteArray(); + } + + public static byte[] getImageBytes(EbtsImage image) throws IOException { + try (FileInputStream fis = new FileInputStream(image.getPath())) { + byte[] buffer = new byte[fis.available()]; + if(fis.read(buffer) <= 0) { + throw new IOException(String.format("Unable to read file %s", image.getPath())); + } + + return buffer; + } + } + + public static byte[] getImageBytesPng(EbtsImage image) throws IOException { + String contentType = image.getOriginalImageType(); + byte[] imageData = EbtsImage.getImageBytes(image); + + if(contentType.equals("image/png")) { + return imageData; + } + + contentType = contentType.replace("image/", "").toUpperCase(); + BufferedImage img = ImageUtils.read(contentType, 16, 16, 8, imageData); + + if(img == null) { + throw new IOException(String.format("Unsupported Image Format (%s)", contentType)); + } + + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ImageIO.write(img, "png", outputStream); + + return outputStream.toByteArray(); + } catch(Exception e) { + logger.info(e.getMessage()); + } + + return new byte[0]; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/data/EbtsImageFormat.java b/web/src/main/java/org/mitre/iwp/web/data/EbtsImageFormat.java new file mode 100644 index 0000000..6b7f92e --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/EbtsImageFormat.java @@ -0,0 +1,23 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +public enum EbtsImageFormat { + NULL(null), + PNG("image/png"); + + private String contentType; + public String getContentType() { return contentType; } + + EbtsImageFormat(String contentType) { + this.contentType = contentType; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/data/FileContentField.java b/web/src/main/java/org/mitre/iwp/web/data/FileContentField.java new file mode 100644 index 0000000..295870e --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/FileContentField.java @@ -0,0 +1,118 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class FileContentField { + private static final Logger log = LoggerFactory.getLogger(FileContentField.class); + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private int firstRecordCategoryCode; + public int getFirstRecordCategoryCode() { return this.firstRecordCategoryCode; } + public void setFirstRecordCategoryCode(int firstRecordCategoryCode) { this.firstRecordCategoryCode = firstRecordCategoryCode; } + + private String contentRecordCount; + public String getContentRecordCount() { + return this.contentRecordCount; + } + public void setContentRecordCount(String contentRecordCount) { this.contentRecordCount = contentRecordCount; } + + @ElementCollection + private List recordCategoryCodes = new ArrayList<>(); + public List getRecordCategoryCodes() { + return this.recordCategoryCodes; + } + public void setRecordCategoryCodes(List recordCategoryCodes) { + this.recordCategoryCodes = recordCategoryCodes; + } + + @ElementCollection + private List informationDesignationCharacters = new ArrayList<>(); + public List getInformationDesignationCharacters() { + return this.informationDesignationCharacters; + } + public void setInformationDesignationCharacters(List informationDesignationCharacters) { + this.informationDesignationCharacters = informationDesignationCharacters; + } + + public FileContentField() { + // Empty constructor to satisfy JPA requirement + } + + public String toString() { + return String.format("%s#", + this.getClass().getSimpleName(), id, firstRecordCategoryCode, contentRecordCount, recordCategoryCodes.size(), informationDesignationCharacters.size()); + } + + public static FileContentField buildFromEbtsField(org.mitre.jet.ebts.field.Field field) { + FileContentField cnt = new FileContentField(); + + if (field == null) { + log.debug("CNT field not provided"); + } else { + for (int i = 0; i < field.getOccurrences().size(); i++) { + Occurrence occurrence = field.getOccurrences().get(i); + + if (occurrence.getSubFields().size() != 2) { + log.debug("CNT occurrence contains an unexpected number of subfields ({}) and will not be parsed", occurrence.getSubFields().size()); + } else { + if (i == 0) { + cnt.firstRecordCategoryCode = EbtsConversionHelper.getIntFromOccurrence(occurrence, 0, Integer.MAX_VALUE); + cnt.contentRecordCount = EbtsConversionHelper.getStringFromOccurrence(occurrence, 1); + } else { + cnt.recordCategoryCodes.add(EbtsConversionHelper.getStringFromOccurrence(occurrence, 0)); + cnt.informationDesignationCharacters.add(EbtsConversionHelper.getStringFromOccurrence(occurrence, 1)); + } + } + } + } + + return cnt; + } + + public static List buildEbtsOccurrence(FileContentField fcf) { + List occList = new ArrayList<>(); + Occurrence occurrence = new Occurrence(); + + List subFields = new ArrayList<>(); + subFields.add(EbtsConversionHelper.getSubFieldFromInt(fcf.getFirstRecordCategoryCode())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(fcf.getContentRecordCount())); + occurrence.setSubfields(subFields); + occList.add(occurrence); + occurrence = new Occurrence(); + subFields = new ArrayList<>(); + for (int i = 0; i < fcf.getRecordCategoryCodes().size(); i++) { // getRecordCategoryCodes and getInformationDesignationCharacters 'should' always be equal? + subFields.add(EbtsConversionHelper.getSubFieldFromString(fcf.getRecordCategoryCodes().get(i))); + subFields.add(EbtsConversionHelper.getSubFieldFromString(fcf.getInformationDesignationCharacters().get(i))); + } + + occurrence.setSubfields(subFields); + occList.add(occurrence); + return occList; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/data/ImagePropertyCodeField.java b/web/src/main/java/org/mitre/iwp/web/data/ImagePropertyCodeField.java new file mode 100644 index 0000000..2bbd400 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/ImagePropertyCodeField.java @@ -0,0 +1,84 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class ImagePropertyCodeField { + private static final Logger log = LoggerFactory.getLogger(ImagePropertyCodeField.class); + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private int horizontalOrientationCode; + public int getHorizontalOrientationCode() { return this.horizontalOrientationCode; } + + private int verticalOrientationCode; + public int getVerticalOrientationCode() { return this.verticalOrientationCode; } + + private int specificScanType; + public int getSpecificScanType() { return this.specificScanType; } + + public ImagePropertyCodeField() { + // Empty constructor to satisfy JPA requirement + } + + public String toString() { + return String.format("%s#", + this.getClass().getSimpleName(), id, horizontalOrientationCode, verticalOrientationCode, specificScanType); + } + + public static ImagePropertyCodeField buildFromEbtsField(org.mitre.jet.ebts.field.Field field) { + ImagePropertyCodeField ipcf = new ImagePropertyCodeField(); + + if (field == null) { + log.debug("IPC field not provided"); + } else if (field.getOccurrences().size() != 1) { + log.debug("IPC field contains an unexpected number of occurrences ({}) and will not be parsed.", field.getOccurrences().size()); + } else { + Occurrence occurrence = field.getOccurrences().get(0); + + if (occurrence.getSubFields().size() != 3) { + log.debug("IPC field contains an unexpected number of subfields ({}) and will not be parsed.", occurrence.getSubFields().size()); + } else { + ipcf.horizontalOrientationCode = EbtsConversionHelper.getIntFromOccurrence(occurrence, 0, Integer.MAX_VALUE); + ipcf.verticalOrientationCode = EbtsConversionHelper.getIntFromOccurrence(occurrence, 1, Integer.MAX_VALUE); + ipcf.specificScanType = EbtsConversionHelper.getIntFromOccurrence(occurrence, 2, Integer.MAX_VALUE); + } + } + + return ipcf; + } + + public static Occurrence buildEbtsOccurrence(ImagePropertyCodeField ipc) { + Occurrence occurrence = new Occurrence(); + List subFields = new ArrayList<>(); + subFields.add(EbtsConversionHelper.getSubFieldFromInt(ipc.getHorizontalOrientationCode())); + subFields.add(EbtsConversionHelper.getSubFieldFromInt(ipc.getVerticalOrientationCode())); + subFields.add(EbtsConversionHelper.getSubFieldFromInt(ipc.getSpecificScanType())); + occurrence.setSubfields(subFields); + + return occurrence; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/data/IrisMessage.java b/web/src/main/java/org/mitre/iwp/web/data/IrisMessage.java new file mode 100644 index 0000000..413ab3b --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/IrisMessage.java @@ -0,0 +1,79 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.mitre.jet.ebts.Ebts; +import org.mitre.jet.ebts.records.LogicalRecord; + +import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class IrisMessage { + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String fileHash; + public String getFileHash() { return this.fileHash; } + public void setFileHash(String fileHash) { this.fileHash = fileHash; } + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private Type1 type1; + public Type1 getType1() { return type1; } + public void setType1(Type1 type1){this.type1 = type1;} + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private Type2 type2; + public Type2 getType2() { return type2; } + public void setType2(Type2 type2){this.type2 = type2;} + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + private List type17 = new ArrayList<>(); + public List getType17() { return type17; } + public void setType17(List t17){this.type17 = t17;} + + public IrisMessage() { + // Empty constructor to satisfy JPA requirement + } + + public String toString() { + return String.format("%s<#id='%s'>", + this.getClass().getSimpleName(), + id); + } + + public static IrisMessage buildFromEbts(Ebts ebts) { + IrisMessage iMessage = new IrisMessage(); + for(LogicalRecord logicalRecord : ebts.getAllRecords()) { + switch(logicalRecord.getRecordType()) { + case 1: + iMessage.type1 = Type1.buildFromEbtsRecord(logicalRecord); + break; + case 2: + iMessage.type2 = Type2.buildFromEbtsRecord(logicalRecord); + break; + case 17: + if(iMessage.type17 != null) { + iMessage.type17.add(Type17.buildFromEbtsRecord(logicalRecord)); + } + break; + default: + break; + } + } + + return iMessage; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/data/MakeModelSerialNumberField.java b/web/src/main/java/org/mitre/iwp/web/data/MakeModelSerialNumberField.java new file mode 100644 index 0000000..6faa3e5 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/MakeModelSerialNumberField.java @@ -0,0 +1,83 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import java.util.ArrayList; +import java.util.List; +@Entity +public class MakeModelSerialNumberField { + private static final Logger log = LoggerFactory.getLogger(MakeModelSerialNumberField.class); + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String make; + public String getMake() { return make; } + + private String model; + public String getModel() { return model; } + + private String serialNumber; + public String getSerialNumber() { return serialNumber; } + + public MakeModelSerialNumberField() { + // Empty constructor to satisfy JPA requirement + } + + public String toString() { + return String.format("%s#", + this.getClass().getSimpleName(), id, make, model, serialNumber); + } + + public static MakeModelSerialNumberField buildFromEbtsField(org.mitre.jet.ebts.field.Field field) { + MakeModelSerialNumberField mmsnf = new MakeModelSerialNumberField(); + + if (field == null) { + log.debug("MMS field not provided"); + } else if (field.getOccurrences().size() != 1) { + log.debug("MMS field contains an unexpected number of occurrences ({}) and will not be parsed.", field.getOccurrences().size()); + } else { + Occurrence occurrence = field.getOccurrences().get(0); + + if (occurrence.getSubFields().size() != 3) { + log.debug("MMS field contains an unexpected number of subfields ({}) and will not be parsed.", occurrence.getSubFields().size()); + } else { + mmsnf.make = EbtsConversionHelper.getStringFromOccurrence(occurrence, 0); + mmsnf.model = EbtsConversionHelper.getStringFromOccurrence(occurrence, 1); + mmsnf.serialNumber = EbtsConversionHelper.getStringFromOccurrence(occurrence, 2); + } + } + + return mmsnf; + } + + public static Occurrence buildEbtsOccurrence(MakeModelSerialNumberField mms) { + Occurrence occurrence = new Occurrence(); + List subFields = new ArrayList<>(); + subFields.add(EbtsConversionHelper.getSubFieldFromString(mms.getMake())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(mms.getModel())); + subFields.add(EbtsConversionHelper.getSubFieldFromString(mms.getSerialNumber())); + occurrence.setSubfields(subFields); + + return occurrence; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/data/Type1.java b/web/src/main/java/org/mitre/iwp/web/data/Type1.java new file mode 100644 index 0000000..7b1abd3 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/Type1.java @@ -0,0 +1,135 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.mitre.jet.ebts.records.GenericRecord; +import org.mitre.jet.ebts.records.LogicalRecord; + +import jakarta.persistence.*; + +@Entity +public class Type1 { + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private int length; + public int getLength() { + return this.length; + } + + private String version; + public String getVersion() { + return this.version; + } + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private FileContentField fileContent; + public FileContentField getFileContent() { return this.fileContent; } + + private String typeOfTransaction; + public String getTypeOfTransaction() { return this.typeOfTransaction; } + + private String date; + public String getDate() { return this.date; } + + private String transactionPriority; + public String getTransactionPriority() { return this.transactionPriority; } + + private String destinationAgencyIdentifier; + public String getDestinationAgencyIdentifier() { return this.destinationAgencyIdentifier; } + + private String originatingAgencyIdentifier; + public String getOriginatingAgencyIdentifier() { return this.originatingAgencyIdentifier; } + + private String transactionControlNumber; + public String getTransactionControlNumber() { return this.transactionControlNumber; } + + private String transactionControlReference; + public String getTransactionControlReference() { return this.transactionControlReference; } + + private String nativeScanningResolution; + public String getNativeScanningResolution() { return this.nativeScanningResolution; } + + private String nominalResolution; + public String getNominalResolution() { return this.nominalResolution; } + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private DomainNameField domainName; + public DomainNameField getDomainName() { return this.domainName; } + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private ApplicationProfileSpecificationsField applicationProfileSpecification; + public ApplicationProfileSpecificationsField getApplicationProfileSpecification() { return this.applicationProfileSpecification; } + + + public Type1() { + // Empty constructor to satisfy JPA requirement + } + + public String toString() { + return String.format("%s#", + this.getClass().getSimpleName(), + id); + } + + public static Type1 buildFromEbtsRecord(LogicalRecord logicalRecord) { + Type1 type1 = new Type1(); + type1.length = EbtsConversionHelper.getIntFromField(logicalRecord.getField(1), Integer.MAX_VALUE); + type1.version = EbtsConversionHelper.getStringFromField(logicalRecord.getField(2)); + type1.fileContent = FileContentField.buildFromEbtsField(logicalRecord.getField(3)); + type1.typeOfTransaction = EbtsConversionHelper.getStringFromField(logicalRecord.getField(4)); + type1.date = EbtsConversionHelper.getStringFromField(logicalRecord.getField(5)); + type1.transactionPriority = EbtsConversionHelper.getStringFromField(logicalRecord.getField(6)); + type1.destinationAgencyIdentifier = EbtsConversionHelper.getStringFromField(logicalRecord.getField(7)); + type1.originatingAgencyIdentifier = EbtsConversionHelper.getStringFromField(logicalRecord.getField(8)); + type1.transactionControlNumber = EbtsConversionHelper.getStringFromField(logicalRecord.getField(9)); + type1.transactionControlReference = EbtsConversionHelper.getStringFromField(logicalRecord.getField(10)); + type1.nativeScanningResolution = EbtsConversionHelper.getStringFromField(logicalRecord.getField(11)); + type1.nominalResolution = EbtsConversionHelper.getStringFromField(logicalRecord.getField(12)); + type1.domainName = DomainNameField.buildFromEbtsField(logicalRecord.getField(13)); + type1.applicationProfileSpecification = ApplicationProfileSpecificationsField.buildFromEbtsField(logicalRecord.getField(16)); + return type1; + } + + public static LogicalRecord buildEbtsLogicalRecord(Type1 t1) { + LogicalRecord logicalRecord = new GenericRecord(1); + + logicalRecord.setField(4, EbtsConversionHelper.getFieldFromString(t1.getTypeOfTransaction())); + logicalRecord.setField(5, EbtsConversionHelper.getFieldFromString(t1.getDate())); + logicalRecord.setField(6, EbtsConversionHelper.getFieldFromString(t1.getTransactionPriority())); + logicalRecord.setField(7, EbtsConversionHelper.getFieldFromString(t1.getDestinationAgencyIdentifier())); + logicalRecord.setField(8, EbtsConversionHelper.getFieldFromString(t1.getOriginatingAgencyIdentifier())); + logicalRecord.setField(9, EbtsConversionHelper.getFieldFromString(t1.getTransactionControlNumber())); + logicalRecord.setField(10, EbtsConversionHelper.getFieldFromString(t1.getTransactionControlReference())); + logicalRecord.setField(11, EbtsConversionHelper.getFieldFromString(t1.getNativeScanningResolution())); + logicalRecord.setField(12, EbtsConversionHelper.getFieldFromString(t1.getNominalResolution())); + + // Create 'File Content Field' Occurrence - 1 Occurrence + org.mitre.jet.ebts.field.Field tempField = EbtsConversionHelper.getEmptyField(); + tempField.getOccurrences().addAll(FileContentField.buildEbtsOccurrence(t1.getFileContent())); + logicalRecord.setField(3, tempField); + + // Create 'Domain Name' Occurrence - 1 Occurrence + tempField = EbtsConversionHelper.getEmptyField(); + tempField.getOccurrences().add(DomainNameField.buildEbtsOccurrence(t1.getDomainName())); + logicalRecord.setField(13, tempField); + + // Create 'Application Profile Specifications' Occurrence - 1 Occurrence + tempField = EbtsConversionHelper.getEmptyField(); + tempField.getOccurrences().add(ApplicationProfileSpecificationsField.buildEbtsOccurrence(t1.getApplicationProfileSpecification())); + logicalRecord.setField(16, tempField); + + return logicalRecord; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/data/Type17.java b/web/src/main/java/org/mitre/iwp/web/data/Type17.java new file mode 100644 index 0000000..2bdb1aa --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/data/Type17.java @@ -0,0 +1,276 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.records.GenericRecord; +import org.mitre.jet.ebts.records.LogicalRecord; + +import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class Type17 { + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private int length; + public int getLength() { return length; } + public void setLength( int length ) { this.length = length; } + + private String imageDesignationCharacter; + public String getImageDesignationCharacter() { return this.imageDesignationCharacter; } + public void setImageDesignationCharacter(String imageDesignationCharacter) { this.imageDesignationCharacter = imageDesignationCharacter; } + + private int eyeLabel; + public int getEyeLabel() { return this.eyeLabel; } + public void setEyeLabel(int eyeLabel) { this.eyeLabel = eyeLabel; } + + private String sourceAgency; + public String getSourceAgency() { return this.sourceAgency; } + public void setSourceAgency(String sourceAgency) { this.sourceAgency = sourceAgency; } + + private String irisCaptureDate; + public String getIrisCaptureDate() { return this.irisCaptureDate; } + public void setIrisCaptureDate(String irisCaptureDate) { this.irisCaptureDate = irisCaptureDate; } + + private int horizontalLineLength; + public int getHorizontalLineLength() { return this.horizontalLineLength; } + public void setHorizontalLineLength(int horizontalLineLength) { this.horizontalLineLength = horizontalLineLength; } + + private int verticalLineLength; + public int getVerticalLineLength() { return this.verticalLineLength; } + public void setVerticalLineLength(int verticalLineLength) { this.verticalLineLength = verticalLineLength; } + + private int scaleUnits; + public int getScaleUnits() { return this.scaleUnits; } + public void setScaleUnits(int scaleUnits) { this.scaleUnits = scaleUnits; } + + private int transmittedHorizontalPixelScale; + public int getTransmittedHorizontalPixelScale() { return this.transmittedHorizontalPixelScale; } + public void setTransmittedHorizontalPixelScale(int transmittedHorizontalPixelScale) { this.transmittedHorizontalPixelScale = transmittedHorizontalPixelScale; } + + private int transmittedVerticalPixelScale; + public int getTransmittedVerticalPixelScale() { return this.transmittedVerticalPixelScale; } + public void setTransmittedVerticalPixelScale(int transmittedVerticalPixelScale) { this.transmittedVerticalPixelScale = transmittedVerticalPixelScale; } + + private String compressionAlgorithm; + public String getCompressionAlgorithm() { return this.compressionAlgorithm; } + public void setCompressionAlgorithm(String compressionAlgorithm) { this.compressionAlgorithm = compressionAlgorithm; } + + private int bitsPerPixel; + public int getBitsPerPixel() { return this.bitsPerPixel; } + public void setBitsPerPixel(int bitsPerPixel) { this.bitsPerPixel = bitsPerPixel; } + + private String colorSpace; + public String getColorSpace() { return this.colorSpace; } + public void setColorSpace(String colorSpace) { this.colorSpace = colorSpace; } + + private int rotationAngleOfEye; + public int getRotationAngleOfEye() { return this.rotationAngleOfEye; } + public void setRotationAngleOfEye(int rotationAngleOfEye) { this.rotationAngleOfEye = rotationAngleOfEye; } + + private String rotationUncertainty; + public String getRotationUncertainty() { return this.rotationUncertainty; } + public void setRotationUncertainty(String rotationUncertainty) { this.rotationUncertainty = rotationUncertainty; } + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private ImagePropertyCodeField imagePropertyCodeField; + public ImagePropertyCodeField getImagePropertyCodeField() { return this.imagePropertyCodeField; } + public void setImagePropertyCodeField(ImagePropertyCodeField imagePropertyCodeField) { this.imagePropertyCodeField = imagePropertyCodeField; } + + private String deviceUniqueIdentifier; + public String getDeviceUniqueIdentifier() { return this.deviceUniqueIdentifier; } + public void setDeviceUniqueIdentifier(String deviceUniqueIdentifier) { this.deviceUniqueIdentifier = deviceUniqueIdentifier; } + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private MakeModelSerialNumberField makeModelSerialNumberField; + public MakeModelSerialNumberField getMakeModelSerialNumberField() { return this.makeModelSerialNumberField; } + public void setMakeModelSerialNumberField(MakeModelSerialNumberField makeModelSerialNumberField) { this.makeModelSerialNumberField = makeModelSerialNumberField; } + + private String eyeColor; + public String getEyeColor() { return this.eyeColor; } + public void setEyeColor(String eyeColor) { this.eyeColor = eyeColor; } + + private String comment; + public String getComment() { return this.comment; } + public void setComment(String comment) { this.comment = comment; } + + private String effectiveAcquisitionSpectrum; + public String getEffectiveAcquisitionSpectrum() { return this.effectiveAcquisitionSpectrum; } + public void setEffectiveAcquisitionSpectrum(String effectiveAcquisitionSpectrum) { this.effectiveAcquisitionSpectrum = effectiveAcquisitionSpectrum; } + + private String damagedOrMissingEye; + public String getDamagedOrMissingEye() { return this.damagedOrMissingEye; } + public void setDamagedOrMissingEye(String damagedOrMissingEye) { this.damagedOrMissingEye = damagedOrMissingEye; } + + private int subjectAcquisitionProfileIris; + public int getSubjectAcquisitionProfileIris() { return this.subjectAcquisitionProfileIris; } + public void setSubjectAcquisitionProfileIris(int subjectAcquisitionProfileIris) { this.subjectAcquisitionProfileIris = subjectAcquisitionProfileIris; } + + private int irisStorageFormat; + public int getIrisStorageFormat() { return this.irisStorageFormat; } + public void setIrisStorageFormat(int irisStorageFormat) { this.irisStorageFormat = irisStorageFormat; } + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + private List annotationInformationFieldList = new ArrayList<>(); + public List getAnnotationInformationFieldList() { return this.annotationInformationFieldList; } + public void setAnnotationInformationFieldList( List annotationInformationFieldList ) { + this.annotationInformationFieldList = annotationInformationFieldList; + } + + @Lob + @Column(columnDefinition="LONGBLOB") + private byte[] data; //17.999 + public byte[] getData() { return data; } + public void setData( byte[] data ) { this.data = data; } + + private long ebtsImageId; + public long getEbtsImageId() { return this.ebtsImageId; } + public void setEbtsImageId( long ebtsImageId ) { this.ebtsImageId = ebtsImageId; } + + private static final String imageNoneMessage = "image/none"; + + public Type17() { + // Empty constructor to satisfy JPA requirement + } + + public static Type17 buildFromEbtsRecord(LogicalRecord logicalRecord) { + Type17 type17 = new Type17(); + + type17.length = EbtsConversionHelper.getIntFromField(logicalRecord.getField(1), Integer.MAX_VALUE); + type17.imageDesignationCharacter = EbtsConversionHelper.getStringFromField(logicalRecord.getField(2)); + type17.eyeLabel = EbtsConversionHelper.getIntFromField(logicalRecord.getField(3), Integer.MAX_VALUE); + type17.sourceAgency = EbtsConversionHelper.getStringFromField(logicalRecord.getField(4)); + type17.irisCaptureDate = EbtsConversionHelper.getStringFromField(logicalRecord.getField(5)); + type17.horizontalLineLength = EbtsConversionHelper.getIntFromField(logicalRecord.getField(6), Integer.MAX_VALUE); + type17.verticalLineLength = EbtsConversionHelper.getIntFromField(logicalRecord.getField(7), Integer.MAX_VALUE); + type17.scaleUnits = EbtsConversionHelper.getIntFromField(logicalRecord.getField(8), Integer.MAX_VALUE); + type17.transmittedHorizontalPixelScale = EbtsConversionHelper.getIntFromField(logicalRecord.getField(9), Integer.MAX_VALUE); + type17.transmittedVerticalPixelScale = EbtsConversionHelper.getIntFromField(logicalRecord.getField(10), Integer.MAX_VALUE); + type17.compressionAlgorithm = EbtsConversionHelper.getStringFromField(logicalRecord.getField(11)); + type17.bitsPerPixel = EbtsConversionHelper.getIntFromField(logicalRecord.getField(12), Integer.MAX_VALUE); + type17.colorSpace = EbtsConversionHelper.getStringFromField(logicalRecord.getField(13)); + type17.rotationAngleOfEye = EbtsConversionHelper.getIntFromField(logicalRecord.getField(14), Integer.MAX_VALUE); + type17.rotationUncertainty = EbtsConversionHelper.getStringFromField(logicalRecord.getField(15)); + type17.imagePropertyCodeField = ImagePropertyCodeField.buildFromEbtsField(logicalRecord.getField(16)); + type17.deviceUniqueIdentifier = EbtsConversionHelper.getStringFromField(logicalRecord.getField(17)); + type17.makeModelSerialNumberField = MakeModelSerialNumberField.buildFromEbtsField(logicalRecord.getField(19)); + type17.eyeColor = EbtsConversionHelper.getStringFromField(logicalRecord.getField(20)); + type17.comment = EbtsConversionHelper.getStringFromField(logicalRecord.getField(21)); + type17.damagedOrMissingEye = EbtsConversionHelper.getStringFromField(logicalRecord.getField(28)); + type17.effectiveAcquisitionSpectrum = EbtsConversionHelper.getStringFromField(logicalRecord.getField(25)); + type17.subjectAcquisitionProfileIris = EbtsConversionHelper.getIntFromField(logicalRecord.getField(31), Integer.MAX_VALUE); + type17.irisStorageFormat = EbtsConversionHelper.getIntFromField(logicalRecord.getField(32), Integer.MAX_VALUE); + + org.mitre.jet.ebts.field.Field field = logicalRecord.getField(902); // ann + if(field != null){ + for(Occurrence occurrence : field.getOccurrences()) { + type17.annotationInformationFieldList.add(AnnotationInformationField.buildFromEbtsOccurence(occurrence)); + } + } + + type17.data = logicalRecord.getImageData(); + + return type17; + } + + public String toString() { + return String.format("%s# sendCopyTo = new ArrayList<>(); + public List getSendCopyTo() { + return this.sendCopyTo; + } + + private String originatingAgencyCaseNumber; + public String getOriginatingAgencyCaseNumber() { + return this.originatingAgencyCaseNumber; + } + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + private List contributorCaseIdentifierNumber = new ArrayList<>(); + public List getContributorCaseIdentifierNumber() { + return contributorCaseIdentifierNumber; + } + + @ElementCollection + private List contributorCaseIdExtension = new ArrayList<>(); + public List getContributorCaseIdExtension() { + return this.contributorCaseIdExtension; + } + + private String universalControlNumber; + public String getUniversalControlNumber() { + return this.universalControlNumber; + } + + @ElementCollection + private List stateIdentificationNumber = new ArrayList<>(); + public List getStateIdentificationNumber() { + return this.stateIdentificationNumber; + } + + @ElementCollection + private List miscellaneousIdentificationNumber = new ArrayList<>(); + public List getMiscellaneousIdentificationNumber() { + return this.miscellaneousIdentificationNumber; + } + + private String name; + public String getName() { return this.name; } + public void setName(String name) { this.name = name; } + + private String placeOfBirth; + public String getPlaceOfBirth() { return this.placeOfBirth; } + + private String citizenship; + public String getCitizenship() { return this.citizenship; } + + private String dateOfBirth; + public String getDateOfBirth() { return this.dateOfBirth; } + + private int ageRange; + public int getAgeRange() { return this.ageRange; } + + private String sex; + public String getSex() { return this.sex; } + + private String race; + public String getRace() { return this.race; } + + private String scarsMarksAndTattoos; + public String getScarsMarksAndTattoos() { + return this.scarsMarksAndTattoos; + } + + private int heightRange; + public int getHeightRange() { + return this.heightRange; + } + + private int weightRange; + public int getWeightRange() { + return this.weightRange; + } + + private String eyeColor; + public String getEyeColor() { + return this.eyeColor; + } + + private String hairColor; + public String getHairColor() { + return this.hairColor; + } + + private String photoAvailableIndicator; + public String getPhotoAvailableIndicator() { + return this.photoAvailableIndicator; + } + + private String datePrinted; + public String getDatePrinted() { + return this.datePrinted; + } + + private String searchResultFindings; + public String getSearchResultFindings() { + return this.searchResultFindings; + } + + @ElementCollection + private List statusErrorMessage = new ArrayList<>(); + public List getStatusErrorMessage() { + return this.statusErrorMessage; + } + + @ElementCollection + private List imageType = new ArrayList<>(); + public List getImageType() { + return this.imageType; + } + + @ElementCollection + private List controllingAgencyIdentifier = new ArrayList<>(); + public List getControllingAgencyIdentifier() { + return this.controllingAgencyIdentifier; + } + + private int numberOfCandidatesReturned; + public int getNumberOfCandidatesReturned() { + return this.numberOfCandidatesReturned; + } + + private String noteField; + public String getNoteField() { + return this.noteField; + } + + private String requestPhotoRecord; + public String getRequestPhotoRecord() { + return this.requestPhotoRecord; + } + + private int numberOfImagesRequested; + public int getNumberOfImagesRequested() { + return this.numberOfImagesRequested; + } + + private String supplementaryIdentityInformation; + public String getSupplementaryIdentityInformation() { + return this.supplementaryIdentityInformation; + } + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + private List biometricImageDescription = new ArrayList<>(); + public List getBiometricImageDescription() { + return this.biometricImageDescription; + } + + private String biometricSetIdentifier; + public String getBiometricSetIdentifier() { + return this.biometricSetIdentifier; + } + + private int biometricImageAvailable; + public int getBiometricImageAvailable() { + return this.biometricImageAvailable; + } + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + private List candidateInvesitgativeListField = new ArrayList<>(); + public List getCandidateInvesitgativeListField() { + return this.candidateInvesitgativeListField; + } + + private String eventIdentifier; + public String getEventIdentifier() { + return this.eventIdentifier; + } + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + private List biometricImageEnrollment = new ArrayList<>(); + public List getBiometricImageEnrollment() { + return this.biometricImageEnrollment; + } + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + private List biometricImageList = new ArrayList<>(); + public List getBiometricImageList() { + return this.biometricImageList; + } + + public Type2() { + // Empty constructor to satisfy JPA requirement + } + + public String toString() { + return String.format("%s#", this.getClass().getSimpleName(), id); + } + + public static Type2 buildFromEbtsRecord(LogicalRecord logicalRecord) { + Type2 t2 = new Type2(); + + t2.length = EbtsConversionHelper.getIntFromField(logicalRecord.getField(1), Integer.MAX_VALUE); // len + t2.informationDesignationCharacter = EbtsConversionHelper.getStringFromField(logicalRecord.getField(2)); // idc + t2.attentionIndicator = EbtsConversionHelper.getStringFromField(logicalRecord.getField(6)); // atn + t2.originatingAgencyCaseNumber = EbtsConversionHelper.getStringFromField(logicalRecord.getField(9)); // oca + t2.universalControlNumber = EbtsConversionHelper.getStringFromField(logicalRecord.getField(14)); // ucn + t2.name = EbtsConversionHelper.getStringFromField(logicalRecord.getField(18)); // nam + t2.placeOfBirth = EbtsConversionHelper.getStringFromField(logicalRecord.getField(20)); // pob + t2.citizenship = EbtsConversionHelper.getStringFromField(logicalRecord.getField(21)); // ctz + t2.dateOfBirth = EbtsConversionHelper.getStringFromField(logicalRecord.getField(22)); // dob + t2.ageRange = EbtsConversionHelper.getIntFromField(logicalRecord.getField(23), Integer.MAX_VALUE); // agr + t2.sex = EbtsConversionHelper.getStringFromField(logicalRecord.getField(24)); // sex + t2.race = EbtsConversionHelper.getStringFromField(logicalRecord.getField(25)); // rac + t2.scarsMarksAndTattoos = EbtsConversionHelper.getStringFromField(logicalRecord.getField(26)); // smt + t2.heightRange = EbtsConversionHelper.getIntFromField(logicalRecord.getField(28), Integer.MAX_VALUE); // htr + t2.weightRange = EbtsConversionHelper.getIntFromField(logicalRecord.getField(30), Integer.MAX_VALUE); // wtr + t2.eyeColor = EbtsConversionHelper.getStringFromField(logicalRecord.getField(31)); // eye + t2.hairColor = EbtsConversionHelper.getStringFromField(logicalRecord.getField(32)); // hai + t2.photoAvailableIndicator = EbtsConversionHelper.getStringFromField(logicalRecord.getField(36)); // pht + t2.datePrinted = EbtsConversionHelper.getStringFromField(logicalRecord.getField(38)); // dpr + t2.searchResultFindings = EbtsConversionHelper.getStringFromField(logicalRecord.getField(59)); // srf + t2.numberOfCandidatesReturned = EbtsConversionHelper.getIntFromField(logicalRecord.getField(79), Integer.MAX_VALUE); // ncr + t2.noteField = EbtsConversionHelper.getStringFromField(logicalRecord.getField(88)); // not + t2.requestPhotoRecord = EbtsConversionHelper.getStringFromField(logicalRecord.getField(96)); // rpr + t2.numberOfImagesRequested = EbtsConversionHelper.getIntFromField(logicalRecord.getField(2010), Integer.MAX_VALUE); // nir + t2.supplementaryIdentityInformation = EbtsConversionHelper.getStringFromField(logicalRecord.getField(2023)); // sii + t2.biometricSetIdentifier = EbtsConversionHelper.getStringFromField(logicalRecord.getField(2029)); // bsi + t2.biometricImageAvailable = EbtsConversionHelper.getIntFromField(logicalRecord.getField(2031), Integer.MAX_VALUE); // bia + t2.eventIdentifier = EbtsConversionHelper.getStringFromField(logicalRecord.getField(2035)); // evi + + org.mitre.jet.ebts.field.Field field = logicalRecord.getField(7); // sco + if (field != null) { + for (Occurrence occurrence : field.getOccurrences()) { + t2.sendCopyTo.add(EbtsConversionHelper.getStringFromOccurrence(occurrence, 0)); + } + } + + field = logicalRecord.getField(10); // cin + if (field != null) { + for (Occurrence occurrence : field.getOccurrences()) { + t2.contributorCaseIdentifierNumber.add(ContributorCaseIdentifierNumberField.buildFromEbtsOccurence(occurrence)); + } + } + + field = logicalRecord.getField(11); // cix + if (field != null) { + for (Occurrence occurrence : field.getOccurrences()) { + t2.contributorCaseIdExtension.add(EbtsConversionHelper.getIntFromOccurrence(occurrence, 0, Integer.MAX_VALUE)); + } + } + + field = logicalRecord.getField(15); // sid + if (field != null) { + for (Occurrence occurrence : field.getOccurrences()) { + t2.stateIdentificationNumber.add(EbtsConversionHelper.getStringFromOccurrence(occurrence, 0)); + } + } + + field = logicalRecord.getField(17); // mnu + if (field != null) { + for (Occurrence occurrence : field.getOccurrences()) { + t2.miscellaneousIdentificationNumber.add(EbtsConversionHelper.getStringFromOccurrence(occurrence, 0)); + } + } + + field = logicalRecord.getField(60); // msg + if (field != null) { + for (Occurrence occurrence : field.getOccurrences()) { + t2.statusErrorMessage.add(EbtsConversionHelper.getStringFromOccurrence(occurrence, 0)); + } + } + + field = logicalRecord.getField(62); // imt + if (field != null) { + for (Occurrence occurrence : field.getOccurrences()) { + t2.imageType.add(EbtsConversionHelper.getStringFromOccurrence(occurrence, 0)); + } + } + + field = logicalRecord.getField(73); // cri + if (field != null) { + for (Occurrence occurrence : field.getOccurrences()) { + t2.controllingAgencyIdentifier.add(EbtsConversionHelper.getStringFromOccurrence(occurrence, 0)); + } + } + + field = logicalRecord.getField(2028); // bid + if (field != null) { + for (Occurrence occurrence : field.getOccurrences()) { + t2.biometricImageDescription.add(BiometricImageDescriptionField.buildFromEbtsOccurence(occurrence)); + } + } + + field = logicalRecord.getField(2033); // cnl + if (field != null) { + for (Occurrence occurrence : field.getOccurrences()) { + t2.candidateInvesitgativeListField.add(CandidateInvesitgativeListField.buildFromEbtsOccurence(occurrence)); + } + } + + field = logicalRecord.getField(2061); // bie + if (field != null) { + for (Occurrence occurrence : field.getOccurrences()) { + t2.biometricImageEnrollment.add(BiometricImageEnrollmentField.buildFromEbtsOccurence(occurrence)); + } + } + + field = logicalRecord.getField(2073); // bil + if (field != null) { + for (Occurrence occurrence : field.getOccurrences()) { + t2.biometricImageList.add(BiometricImageListField.buildFromEbtsOccurence(occurrence)); + } + } + + return t2; + } + + public static LogicalRecord buildEbtsLogicalRecord(Type2 t2) { + LogicalRecord logicalRecord = new GenericRecord(2); + + logicalRecord.setField(6, EbtsConversionHelper.getFieldFromString(t2.getAttentionIndicator())); + logicalRecord.setField(9, EbtsConversionHelper.getFieldFromString(t2.getOriginatingAgencyCaseNumber())); + logicalRecord.setField(14, EbtsConversionHelper.getFieldFromString(t2.getUniversalControlNumber())); + logicalRecord.setField(18, EbtsConversionHelper.getFieldFromString(t2.getName())); + logicalRecord.setField(20, EbtsConversionHelper.getFieldFromString(t2.getPlaceOfBirth())); + logicalRecord.setField(21, EbtsConversionHelper.getFieldFromString(t2.getCitizenship())); + logicalRecord.setField(22, EbtsConversionHelper.getFieldFromString(t2.getDateOfBirth())); + logicalRecord.setField(23, EbtsConversionHelper.getFieldFromInt(t2.getAgeRange())); + logicalRecord.setField(24, EbtsConversionHelper.getFieldFromString(t2.getSex())); + logicalRecord.setField(25, EbtsConversionHelper.getFieldFromString(t2.getRace())); + logicalRecord.setField(26, EbtsConversionHelper.getFieldFromString(t2.getScarsMarksAndTattoos())); + logicalRecord.setField(28, EbtsConversionHelper.getFieldFromInt(t2.getHeightRange())); + logicalRecord.setField(30, EbtsConversionHelper.getFieldFromInt(t2.getWeightRange())); + logicalRecord.setField(31, EbtsConversionHelper.getFieldFromString(t2.getEyeColor())); + logicalRecord.setField(32, EbtsConversionHelper.getFieldFromString(t2.getHairColor())); + logicalRecord.setField(36, EbtsConversionHelper.getFieldFromString(t2.getPhotoAvailableIndicator())); + logicalRecord.setField(38, EbtsConversionHelper.getFieldFromString(t2.getDatePrinted())); + logicalRecord.setField(59, EbtsConversionHelper.getFieldFromString(t2.getSearchResultFindings())); + logicalRecord.setField(79, EbtsConversionHelper.getFieldFromInt(t2.getNumberOfCandidatesReturned())); + logicalRecord.setField(88, EbtsConversionHelper.getFieldFromString(t2.getNoteField())); + logicalRecord.setField(96, EbtsConversionHelper.getFieldFromString(t2.getRequestPhotoRecord())); + logicalRecord.setField(2010, EbtsConversionHelper.getFieldFromInt(t2.getNumberOfImagesRequested())); + logicalRecord.setField(2023, EbtsConversionHelper.getFieldFromString(t2.getSupplementaryIdentityInformation())); + logicalRecord.setField(2029, EbtsConversionHelper.getFieldFromString(t2.getBiometricSetIdentifier())); + logicalRecord.setField(2031, EbtsConversionHelper.getFieldFromInt(t2.getBiometricImageAvailable())); + logicalRecord.setField(2035, EbtsConversionHelper.getFieldFromString(t2.getEventIdentifier())); + + // Create 'Send Copy To' Occurrences - 1 Occurrence per String + org.mitre.jet.ebts.field.Field tempField = EbtsConversionHelper.getEmptyField(); + for (String sendCopy : t2.getSendCopyTo()) { + tempField.getOccurrences().add(EbtsConversionHelper.getOccurrenceFromString(sendCopy)); + } + logicalRecord.setField(7, tempField); + + // Create 'Contributor Class Identifier Number' Occurrences - 1 Occurrence per ContributorCaseIdentifierNumberField + tempField = EbtsConversionHelper.getEmptyField(); + for (ContributorCaseIdentifierNumberField ccin : t2.getContributorCaseIdentifierNumber()) { + tempField.getOccurrences().add(ContributorCaseIdentifierNumberField.buildEbtsOccurrence(ccin)); + } + logicalRecord.setField(10, tempField); + + // Create 'Contributor Case ID Extension' Occurrences - 1 Occurrence per Integer + tempField = EbtsConversionHelper.getEmptyField(); + for (Integer cix : t2.getContributorCaseIdExtension()) { + tempField.getOccurrences().add(EbtsConversionHelper.getOccurrenceFromInt(cix)); + } + logicalRecord.setField(11, tempField); + + // Create 'State Identificatino Number' Occurrences - 1 Occurrence per String + tempField = EbtsConversionHelper.getEmptyField(); + for (String sid : t2.getStateIdentificationNumber()) { + tempField.getOccurrences().add(EbtsConversionHelper.getOccurrenceFromString(sid)); + } + logicalRecord.setField(15, tempField); + + // Create 'Miscellaneous Identification Number' Occurrences - 1 Occurrence per String + tempField = EbtsConversionHelper.getEmptyField(); + for (String mnu : t2.getMiscellaneousIdentificationNumber()) { + tempField.getOccurrences().add(EbtsConversionHelper.getOccurrenceFromString(mnu)); + } + logicalRecord.setField(17, tempField); + + // Create 'Status Error Message' Occurrences- 1 Occurrence per String + tempField = EbtsConversionHelper.getEmptyField(); + for (String msg : t2.getStatusErrorMessage()) { + tempField.getOccurrences().add(EbtsConversionHelper.getOccurrenceFromString(msg)); + } + logicalRecord.setField(60, tempField); + + // Create 'Image Type' Occurrences - 1 Occurrence per String + tempField = EbtsConversionHelper.getEmptyField(); + for (String imt : t2.getImageType()) { + tempField.getOccurrences().add(EbtsConversionHelper.getOccurrenceFromString(imt)); + } + logicalRecord.setField(62, tempField); + + // Create 'Controlling Agency Identifier' Occurrences - 1 Occurrence per String + tempField = EbtsConversionHelper.getEmptyField(); + for (String cri : t2.getControllingAgencyIdentifier()) { + tempField.getOccurrences().add(EbtsConversionHelper.getOccurrenceFromString(cri)); + } + logicalRecord.setField(73, tempField); + + // Create 'Biometric Image Description' Occurrences - 1 Occurrence per BiometricImageDescriptionField object + tempField = EbtsConversionHelper.getEmptyField(); + for (BiometricImageDescriptionField bid : t2.getBiometricImageDescription()) { + tempField.getOccurrences().add(BiometricImageDescriptionField.buildEbtsOccurrence(bid)); + } + logicalRecord.setField(2028, tempField); + + // Create 'Candidate Investigative List' Occurrences - 1 Occurrence per CandidateInvesitgativeListField object + tempField = EbtsConversionHelper.getEmptyField(); + for (CandidateInvesitgativeListField cil : t2.getCandidateInvesitgativeListField()) { + tempField.getOccurrences().add(CandidateInvesitgativeListField.buildEbtsOccurrence(cil)); + } + logicalRecord.setField(2033, tempField); + + // Create 'Biometric Image Enrollment' Occurrences - 1 Occurrence per BiometricImageEnrollmentField object + tempField = EbtsConversionHelper.getEmptyField(); + for (BiometricImageEnrollmentField bie : t2.getBiometricImageEnrollment()) { + tempField.getOccurrences().add(BiometricImageEnrollmentField.buildEbtsOccurrence(bie)); + } + logicalRecord.setField(2061, tempField); + + // Create 'Biometric Image List' Occurrences - 1 Occurrence per BiometricImageListField object + tempField = EbtsConversionHelper.getEmptyField(); + for (BiometricImageListField bil : t2.getBiometricImageList()) { + tempField.getOccurrences().add(BiometricImageListField.buildEbtsOccurrence(bil)); + } + logicalRecord.setField(2073, tempField); + + return logicalRecord; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/AnnotationDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/AnnotationDTO.java new file mode 100644 index 0000000..878c54d --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/AnnotationDTO.java @@ -0,0 +1,92 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import java.util.ArrayList; +import java.util.List; + +public class AnnotationDTO { + public AnnotationDTO(){} + + public AnnotationDTO(String id, String text, String type, Long creationDateEpoch, AnnotationValueDTO value, List tempChildren, String parentId) { + this.id = id; + this.text = text; + this.type = type; + this.creationDateEpoch = creationDateEpoch; + this.value = value; + this.tempChildren = tempChildren; + this.parentId = parentId; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Long getCreationDateEpoch() { + return creationDateEpoch; + } + + public void setCreationDateEpoch(Long creationDateEpoch) { + this.creationDateEpoch = creationDateEpoch; + } + + public AnnotationValueDTO getValue() { + return value; + } + + public void setValue(AnnotationValueDTO value) { + this.value = value; + } + + public List getTempChildren() { + return tempChildren; + } + + public void setTempChildren(List tempChildren) { + this.tempChildren = tempChildren; + } + + public String getParentId() { + return parentId; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } + + String id; + String text; + String type; + Long creationDateEpoch = 0L; + AnnotationValueDTO value = new AnnotationValueDTO(); + List tempChildren = new ArrayList<>(); + String parentId; +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/AnnotationValueDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/AnnotationValueDTO.java new file mode 100644 index 0000000..3165d18 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/AnnotationValueDTO.java @@ -0,0 +1,66 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import org.mitre.iwp.web.model.Point; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AnnotationValueDTO { + public AnnotationValueDTO(){} + + public AnnotationValueDTO(Long id, List points, AnnotationDTO annotation, Map data) { + this.id = id; + this.points = points; + this.annotation = annotation; + this.data = data; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public List getPoints() { + return points; + } + + public void setPoints(List points) { + this.points = points; + } + + public AnnotationDTO getAnnotation() { + return annotation; + } + + public void setAnnotation(AnnotationDTO annotation) { + this.annotation = annotation; + } + + public Map getData() { + return data; + } + + public void setData(Map data) { + this.data = data; + } + + Long id; + List points = new ArrayList<>(); + AnnotationDTO annotation = new AnnotationDTO(); + Map data = new HashMap<>(); +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/AppNotificationDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/AppNotificationDTO.java new file mode 100644 index 0000000..728d5c0 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/AppNotificationDTO.java @@ -0,0 +1,89 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +public class AppNotificationDTO { + public AppNotificationDTO() {} + + public AppNotificationDTO(Long id, Long userId, String message, String icon, String action, String dateCreated, boolean isNew) { + this.id = id; + this.userId = userId; + this.message = message; + this.icon = icon; + this.action = action; + this.dateCreated = dateCreated; + this.isNew = isNew; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public boolean isNew() { + return isNew; + } + + public void setNew(boolean aNew) { + isNew = aNew; + } + + Long id; + Long userId; + String message; + String icon; + String action; + String dateCreated; + boolean isNew; +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/CandidateDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/CandidateDTO.java new file mode 100644 index 0000000..131a8d0 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/CandidateDTO.java @@ -0,0 +1,84 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import org.mitre.iwp.web.model.ImageData; + +import java.util.ArrayList; +import java.util.List; + +public class CandidateDTO { + public CandidateDTO(){} + + public CandidateDTO(Long id, String score, String subjectIdentifier, List imageList, String adjudicationResults, String note) { + this.id = id; + this.score = score; + this.subjectIdentifier = subjectIdentifier; + this.imageList = imageList; + this.adjudicationResults = adjudicationResults; + this.note = note; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getScore() { + return score; + } + + public void setScore(String score) { + this.score = score; + } + + public String getSubjectIdentifier() { + return subjectIdentifier; + } + + public void setSubjectIdentifier(String subjectIdentifier) { + this.subjectIdentifier = subjectIdentifier; + } + + public List getImageList() { + return imageList; + } + + public void setImageList(List imageList) { + this.imageList = imageList; + } + + public String getAdjudicationResults() { + return adjudicationResults; + } + + public void setAdjudicationResults(String adjudicationResults) { + this.adjudicationResults = adjudicationResults; + } + + public String getNote() { + return note; + } + + public void setNote(String note) { + this.note = note; + } + + Long id; + String score; + String subjectIdentifier; + List imageList = new ArrayList<>(); + String adjudicationResults; + String note; +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/CaseInformationDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/CaseInformationDTO.java new file mode 100644 index 0000000..0908c8f --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/CaseInformationDTO.java @@ -0,0 +1,63 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import org.mitre.iwp.web.model.Person; + +public class CaseInformationDTO { + public CaseInformationDTO(){} + + public CaseInformationDTO(Long id, Long comparisonDate, Long receivedDate, Person requestedBy) { + this.id = id; + this.comparisonDate = comparisonDate; + this.receivedDate = receivedDate; + this.requestedBy = requestedBy; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + + public Long getComparisonDate() { + return comparisonDate; + } + + public void setComparisonDate(Long comparisonDate) { + this.comparisonDate = comparisonDate; + } + + public Long getReceivedDate() { + return receivedDate; + } + + public void setReceivedDate(Long receivedDate) { + this.receivedDate = receivedDate; + } + + public Person getRequestedBy() { + return requestedBy; + } + + public void setRequestedBy(Person requestedBy) { + this.requestedBy = requestedBy; + } + + Long id; + Long comparisonDate; + Long receivedDate; + Person requestedBy; + +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/CreateIrisSearchRequestDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/CreateIrisSearchRequestDTO.java new file mode 100644 index 0000000..61752fa --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/CreateIrisSearchRequestDTO.java @@ -0,0 +1,81 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import org.mitre.iwp.web.model.EbtsMessageRequest; + +public class CreateIrisSearchRequestDTO { + public CreateIrisSearchRequestDTO(){} + + public CreateIrisSearchRequestDTO(Long id, EbtsMessageRequest message, UserAuthenticationDTO user, Long creationDateEpoch, Boolean isFinished, String filePath) { + this.id = id; + this.message = message; + this.user = user; + this.creationDateEpoch = creationDateEpoch; + this.isFinished = isFinished; + this.filePath = filePath; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public EbtsMessageRequest getMessage() { + return message; + } + + public void setMessage(EbtsMessageRequest message) { + this.message = message; + } + + public UserAuthenticationDTO getUser() { + return user; + } + + public void setUser(UserAuthenticationDTO user) { + this.user = user; + } + + public Long getCreationDateEpoch() { + return creationDateEpoch; + } + + public void setCreationDateEpoch(Long creationDateEpoch) { + this.creationDateEpoch = creationDateEpoch; + } + + public Boolean getIsFinished() { + return isFinished; + } + + public void setIsFinished(Boolean finished) { + isFinished = finished; + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + Long id; + EbtsMessageRequest message; + UserAuthenticationDTO user = new UserAuthenticationDTO(); + Long creationDateEpoch = 0L; + boolean isFinished = false; + String filePath; +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/EbtsMapper.java b/web/src/main/java/org/mitre/iwp/web/dto/EbtsMapper.java new file mode 100644 index 0000000..094ebf4 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/EbtsMapper.java @@ -0,0 +1,65 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import org.mitre.iwp.web.model.CaseInformation; +import org.mitre.iwp.web.model.Person; + +import java.util.ArrayList; +import java.util.Collection; +import org.springframework.stereotype.Component; + +@Component +public class EbtsMapper { + public CaseInformationDTO toCaseInformationDTO(CaseInformation caseInformation){ + if(caseInformation == null){ + return new CaseInformationDTO(); + } + + return new CaseInformationDTO(caseInformation.getId(), caseInformation.getComparisonDate(), + caseInformation.getReceivedDate(), caseInformation.getRequestedBy()); + } + + public CaseInformation toCaseInformation(CaseInformationDTO caseInformation){ + if(caseInformation == null){ + return new CaseInformation(); + } + + return new CaseInformation(caseInformation.getId(), caseInformation.getComparisonDate(), + caseInformation.getReceivedDate(), caseInformation.getRequestedBy()); + } + + public PersonDTO toPersonDTO(Person person){ + if(person == null){ + return new PersonDTO(); + } + + Collection cases = new ArrayList<>(); + for(CaseInformation caseInformation : person.getCases()){ + cases.add(this.toCaseInformationDTO(caseInformation)); + } + return new PersonDTO(person.getId(), person.getName(), person.getTitle(), person.getDepartment(), person.getAddress(), person.getEmail(), + person.getPhone(), cases); + } + + public Person toPerson(PersonDTO person){ + if(person == null){ + return new Person(); + } + + Collection cases = new ArrayList<>(); + for(CaseInformationDTO caseInformation : person.getCases()){ + cases.add(this.toCaseInformation(caseInformation)); + } + return new Person(person.getId(), person.getName(), person.getTitle(), person.getDepartment(), person.getAddress(), person.getEmail(), + person.getPhone(), cases); + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/EbtsMessageRequestDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/EbtsMessageRequestDTO.java new file mode 100644 index 0000000..5af9e99 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/EbtsMessageRequestDTO.java @@ -0,0 +1,205 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class EbtsMessageRequestDTO { + public EbtsMessageRequestDTO(){} + + public EbtsMessageRequestDTO(Long id, Map mapData, ImageDataDTO imageData, String cinPrefix, String cinIdentifier, int numberOfCandidates, int caseExtension, String dateOfSubmission, String destinationAgencyIdentifier, String originatingAgencyIdentifier, String transactionControlNumber, String attentionIndicator, String sourceAgency, String irisCaptureDate, int rotationOfEye, int rotationUncertainty, List errorMessages, List invalidFields) { + this.id = id; + this.mapData = mapData; + this.imageData = imageData; + this.cinPrefix = cinPrefix; + this.cinIdentifier = cinIdentifier; + this.numberOfCandidates = numberOfCandidates; + this.caseExtension = caseExtension; + this.dateOfSubmission = dateOfSubmission; + this.destinationAgencyIdentifier = destinationAgencyIdentifier; + this.originatingAgencyIdentifier = originatingAgencyIdentifier; + this.transactionControlNumber = transactionControlNumber; + this.attentionIndicator = attentionIndicator; + this.sourceAgency = sourceAgency; + this.irisCaptureDate = irisCaptureDate; + this.rotationOfEye = rotationOfEye; + this.rotationUncertainty = rotationUncertainty; + this.errorMessages = errorMessages; + this.invalidFields = invalidFields; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Map getMapData() { + return mapData; + } + + public void setMapData(Map mapData) { + this.mapData = mapData; + } + + public ImageDataDTO getImageData() { + return imageData; + } + + public void setImageData(ImageDataDTO imageData) { + this.imageData = imageData; + } + + public String getCinPrefix() { + return cinPrefix; + } + + public void setCinPrefix(String cinPrefix) { + this.cinPrefix = cinPrefix; + } + + public String getCinIdentifier() { + return cinIdentifier; + } + + public void setCinIdentifier(String cinIdentifier) { + this.cinIdentifier = cinIdentifier; + } + + public int getNumberOfCandidates() { + return numberOfCandidates; + } + + public void setNumberOfCandidates(int numberOfCandidates) { + this.numberOfCandidates = numberOfCandidates; + } + + public int getCaseExtension() { + return caseExtension; + } + + public void setCaseExtension(int caseExtension) { + this.caseExtension = caseExtension; + } + + public String getDateOfSubmission() { + return dateOfSubmission; + } + + public void setDateOfSubmission(String dateOfSubmission) { + this.dateOfSubmission = dateOfSubmission; + } + + public String getDestinationAgencyIdentifier() { + return destinationAgencyIdentifier; + } + + public void setDestinationAgencyIdentifier(String destinationAgencyIdentifier) { + this.destinationAgencyIdentifier = destinationAgencyIdentifier; + } + + public String getOriginatingAgencyIdentifier() { + return originatingAgencyIdentifier; + } + + public void setOriginatingAgencyIdentifier(String originatingAgencyIdentifier) { + this.originatingAgencyIdentifier = originatingAgencyIdentifier; + } + + public String getTransactionControlNumber() { + return transactionControlNumber; + } + + public void setTransactionControlNumber(String transactionControlNumber) { + this.transactionControlNumber = transactionControlNumber; + } + + public String getAttentionIndicator() { + return attentionIndicator; + } + + public void setAttentionIndicator(String attentionIndicator) { + this.attentionIndicator = attentionIndicator; + } + + public String getSourceAgency() { + return sourceAgency; + } + + public void setSourceAgency(String sourceAgency) { + this.sourceAgency = sourceAgency; + } + + public String getIrisCaptureDate() { + return irisCaptureDate; + } + + public void setIrisCaptureDate(String irisCaptureDate) { + this.irisCaptureDate = irisCaptureDate; + } + + public int getRotationOfEye() { + return rotationOfEye; + } + + public void setRotationOfEye(int rotationOfEye) { + this.rotationOfEye = rotationOfEye; + } + + public int getRotationUncertainty() { + return rotationUncertainty; + } + + public void setRotationUncertainty(int rotationUncertainty) { + this.rotationUncertainty = rotationUncertainty; + } + + public List getErrorMessages() { + return errorMessages; + } + + public void setErrorMessages(List errorMessages) { + this.errorMessages = errorMessages; + } + + public List getInvalidFields() { + return invalidFields; + } + + public void setInvalidFields(List invalidFields) { + this.invalidFields = invalidFields; + } + + Long id; + Map mapData = new HashMap<>(); + ImageDataDTO imageData = new ImageDataDTO(); + String cinPrefix = ""; // 2.010a - cin_pre + String cinIdentifier = ""; // 2.010b - cin_id + int numberOfCandidates; // 2.079 - ncr + int caseExtension; //2.011 - cix + String dateOfSubmission = ""; // 1.005 - dat + String destinationAgencyIdentifier = ""; // 1.007 - dai + String originatingAgencyIdentifier; // 1.008 - ori + String transactionControlNumber; // 1.009 - tcn + String attentionIndicator; // 2.006 - atn + String sourceAgency; // 17.004 - src + String irisCaptureDate; // 17.005 - icd + int rotationOfEye; //17.014 - rae + int rotationUncertainty; // 17.015 - rau + List errorMessages = new ArrayList<>(); + List invalidFields = new ArrayList<>(); + +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/ImageDataDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/ImageDataDTO.java new file mode 100644 index 0000000..fee1b32 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/ImageDataDTO.java @@ -0,0 +1,207 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import org.mitre.iwp.web.model.ContactType; +import org.mitre.iwp.web.model.EyeLabel; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ImageDataDTO { + public ImageDataDTO(){} + + public ImageDataDTO(Long id, Long ebtsImageId, Long unrollImageId, Map unrollRingData, + Long rasterizeEbtsImageId, EyeLabel eyeLabel, ContactType contactType, + List annotations, String aciiHorizontal, boolean reported, + String imageNotes, String dme, String imageData, String unrollImageData, + byte[] imageBytes, byte[] unrollImageBytes, + int recordType, int recordIndex, int qualityScore) { + this.id = id; + this.ebtsImageId = ebtsImageId; + this.unrollImageId = unrollImageId; + this.unrollRingData = unrollRingData; + this.rasterizeEbtsImageId = rasterizeEbtsImageId; + this.eyeLabel = eyeLabel; + this.contactType = contactType; + this.annotations = annotations; + this.aciiHorizontal = aciiHorizontal; + this.reported = reported; + this.imageNotes = imageNotes; + this.dme = dme; + this.imageData = imageData; + this.unrollImageData = unrollImageData; + this.imageBytes = imageBytes; + this.unrollImageBytes = unrollImageBytes; + this.recordType = recordType; + this.recordIndex = recordIndex; + this.qualityScore = qualityScore; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getEbtsImageId() { + return ebtsImageId; + } + + public void setEbtsImageId(Long ebtsImageId) { + this.ebtsImageId = ebtsImageId; + } + + public Long getUnrollImageId() { + return unrollImageId; + } + + public void setUnrollImageId(Long unrollImageId) { + this.unrollImageId = unrollImageId; + } + + public Map getUnrollRingData() { + return unrollRingData; + } + + public void setUnrollRingData(Map unrollRingData) { + this.unrollRingData = unrollRingData; + } + + public Long getRasterizeEbtsImageId() { + return rasterizeEbtsImageId; + } + + public void setRasterizeEbtsImageId(Long rasterizeEbtsImageId) { + this.rasterizeEbtsImageId = rasterizeEbtsImageId; + } + + public EyeLabel getEyeLabel() { + return eyeLabel; + } + + public void setEyeLabel(EyeLabel eyeLabel) { + this.eyeLabel = eyeLabel; + } + + public ContactType getContactType() { + return contactType; + } + + public void setContactType(ContactType contactType) { + this.contactType = contactType; + } + + public List getAnnotations() { + return annotations; + } + + public void setAnnotations(List annotations) { + this.annotations = annotations; + } + + public String getAciiHorizontal() { + return aciiHorizontal; + } + + public void setAciiHorizontal(String aciiHorizontal) { + this.aciiHorizontal = aciiHorizontal; + } + + public boolean isReported() { + return reported; + } + + public void setReported(boolean reported) { + this.reported = reported; + } + + public String getImageNotes() { + return imageNotes; + } + + public void setImageNotes(String imageNotes) { + this.imageNotes = imageNotes; + } + + public String getDme() { + return dme; + } + + public void setDme(String dme) { + this.dme = dme; + } + + public String getImageData() { + return imageData; + } + + public void setImageData(String imageData) { + this.imageData = imageData; + } + + public String getUnrollImageData() { + return unrollImageData; + } + + public void setUnrollImageData(String unrollImageData) { + this.unrollImageData = unrollImageData; + } + + public byte[] getImageBytes() { + return imageBytes; + } + + public void setImageBytes(byte[] imageBytes) { + this.imageBytes = imageBytes; + } + + public byte[] getUnrollImageBytes() { + return unrollImageBytes; + } + + public void setUnrollImageBytes(byte[] unrollImageBytes) { + this.unrollImageBytes = unrollImageBytes; + } + + public int getRecordType() { return this.recordType; } + public void setRecordType(int recordType) { this.recordType = recordType; } + + public int getRecordIndex() { return this.recordIndex; } + public void setRecordIndex(int recordIndex) { this.recordIndex = recordIndex; } + + public int getQualityScore() { return this.qualityScore; } + public void setQualityScore(int qualityScore) { this.qualityScore = qualityScore; } + + Long id; + Long ebtsImageId; + Long unrollImageId; + Map unrollRingData = new HashMap<>(); + Long rasterizeEbtsImageId; + EyeLabel eyeLabel; + ContactType contactType; + List annotations = new ArrayList<>(); + String aciiHorizontal; + boolean reported = false; + String imageNotes = ""; + String dme = ""; + String imageData; + String unrollImageData; + byte[] imageBytes; + byte[] unrollImageBytes = new byte[0]; + private int recordIndex; + private int recordType; + private int qualityScore; +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/ImageMessageMapper.java b/web/src/main/java/org/mitre/iwp/web/dto/ImageMessageMapper.java new file mode 100644 index 0000000..e3c00a9 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/ImageMessageMapper.java @@ -0,0 +1,341 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import org.mitre.iwp.web.model.*; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class ImageMessageMapper { + UserMapper userMapper; + + EbtsMapper ebtsMapper; + + public ImageMessageMapper(UserMapper userMapper, + EbtsMapper ebtsMapper){ + this.userMapper = userMapper; + this.ebtsMapper = ebtsMapper; + } + + + public ReportDataDTO toReportDataDTO(ReportData reportData){ + if(reportData == null){ + return new ReportDataDTO(); + } + + List candidateDTOCollection = new ArrayList<>(); + + for(Candidate candidate : reportData.getCandidates()){ + candidateDTOCollection.add(this.toCandidateDTO(candidate)); + } + + return new ReportDataDTO(reportData.getId(), + reportData.getFileName(), + this.toProbeDTO(reportData.getProbe()), + candidateDTOCollection, + reportData.getIncludeExclude(), + reportData.getResponseType(), + this.ebtsMapper.toCaseInformationDTO(reportData.getCaseInformation()), + reportData.getResults(), + reportData.getInvalidFields(), + reportData.getErrorMessages(), + this.userMapper.toUserAuthenticationDTO(reportData.getCreatedBy()), + this.userMapper.toUserAuthenticationDTO(reportData.getWorkedBy()), + reportData.getIsFinished(), + reportData.getAllowNullRequester()); + } + + public ReportData toReportData(ReportDataDTO reportData){ + if(reportData == null){ + return new ReportData(); + } + + List candidateCollection = new ArrayList<>(); + + for(CandidateDTO candidate : reportData.getCandidates()){ + candidateCollection.add(this.toCandidate(candidate)); + } + + return new ReportData(reportData.getId(), + reportData.getFileName(), + this.toProbe(reportData.getProbe()), + candidateCollection, + reportData.isIncludeExclude(), + reportData.getResponseType(), + this.ebtsMapper.toCaseInformation(reportData.getCaseInformation()), + reportData.getResults(), + reportData.getInvalidFields(), + reportData.getErrorMessages(), + this.userMapper.toUserAuthentication(reportData.getCreatedBy()), + this.userMapper.toUserAuthentication(reportData.getWorkedBy()), + reportData.getIsFinished(), + reportData.getAllowNullRequester()); + } + + public CreateIrisSearchRequestDTO toCreateIrisSearchRequestDTO(CreateIrisSearchRequest createIrisSearchRequest){ + if(createIrisSearchRequest == null){ + return new CreateIrisSearchRequestDTO(); + } + + return new CreateIrisSearchRequestDTO( + createIrisSearchRequest.getId(), + createIrisSearchRequest.getMessage(), + this.userMapper.toUserAuthenticationDTO(createIrisSearchRequest.getUser()), + createIrisSearchRequest.getCreationDateEpoch(), + createIrisSearchRequest.getIsFinished(), + createIrisSearchRequest.getFilePath()); + } + + public CreateIrisSearchRequest toCreateIrisSearchRequest(CreateIrisSearchRequestDTO createIrisSearchRequest){ + if(createIrisSearchRequest == null){ + return new CreateIrisSearchRequest(); + } + + return new CreateIrisSearchRequest(createIrisSearchRequest.getId(), + createIrisSearchRequest.getMessage(), + this.userMapper.toUserAuthentication(createIrisSearchRequest.getUser()), + createIrisSearchRequest.getCreationDateEpoch(), + createIrisSearchRequest.getIsFinished(), + createIrisSearchRequest.getFilePath()); + } + + public EbtsMessageRequestDTO toEbtsMessageRequestDTO(EbtsMessageRequest ebtsMessageRequest){ + if(ebtsMessageRequest == null){ + return new EbtsMessageRequestDTO(); + } + + return new EbtsMessageRequestDTO(ebtsMessageRequest.getId(), + ebtsMessageRequest.getMapData(), + this.toImageDataDTO(ebtsMessageRequest.getImageData()), + ebtsMessageRequest.getCinPrefix(), + ebtsMessageRequest.getCinIdentifier(), + ebtsMessageRequest.getNumberOfCandidates(), + ebtsMessageRequest.getCaseExtension(), + ebtsMessageRequest.getDateOfSubmission(), + ebtsMessageRequest.getDestinationAgencyIdentifier(), + ebtsMessageRequest.getOriginatingAgencyIdentifier(), + ebtsMessageRequest.getTransactionControlNumber(), + ebtsMessageRequest.getAttentionIndicator(), + ebtsMessageRequest.getSourceAgency(), + ebtsMessageRequest.getIrisCaptureDate(), + ebtsMessageRequest.getRotationOfEye(), + ebtsMessageRequest.getRotationUncertainty(), + ebtsMessageRequest.getErrorMessages(), + ebtsMessageRequest.getInvalidFields()); + } + + public EbtsMessageRequest toEbtsMessageRequest(EbtsMessageRequestDTO ebtsMessageRequest){ + if(ebtsMessageRequest == null){ + return new EbtsMessageRequest(); + } + + return new EbtsMessageRequest(ebtsMessageRequest.getId(), + ebtsMessageRequest.getMapData(), + this.toImageData(ebtsMessageRequest.getImageData()), + ebtsMessageRequest.getCinPrefix(), + ebtsMessageRequest.getCinIdentifier(), + ebtsMessageRequest.getNumberOfCandidates(), + ebtsMessageRequest.getCaseExtension(), + ebtsMessageRequest.getDateOfSubmission(), + ebtsMessageRequest.getDestinationAgencyIdentifier(), + ebtsMessageRequest.getOriginatingAgencyIdentifier(), + ebtsMessageRequest.getTransactionControlNumber(), + ebtsMessageRequest.getAttentionIndicator(), + ebtsMessageRequest.getSourceAgency(), + ebtsMessageRequest.getIrisCaptureDate(), + ebtsMessageRequest.getRotationOfEye(), + ebtsMessageRequest.getRotationUncertainty(), + ebtsMessageRequest.getErrorMessages(), + ebtsMessageRequest.getInvalidFields()); + } + + public ProbeDTO toProbeDTO(Probe probe){ + if(probe == null){ + return new ProbeDTO(); + } + + return new ProbeDTO(probe.getId(), + probe.getTransactionControlReference(), + probe.getTransactionControlNumber(), + probe.getImageList(), + probe.getName(), + probe.getSubjectIdentifier()); + } + + public Probe toProbe(ProbeDTO probe){ + if(probe == null){ + return new Probe(); + } + + return new Probe(probe.getId(), + probe.getTransactionControlReference(), + probe.getTransactionControlNumber(), + probe.getImageList(), + probe.getName(), + probe.getSubjectIdentifier()); + } + + public CandidateDTO toCandidateDTO(Candidate candidate){ + if(candidate == null){ + return new CandidateDTO(); + } + + return new CandidateDTO(candidate.getId(), + candidate.getScore(), + candidate.getSubjectIdentifier(), + candidate.getImageList(), + candidate.getAdjudicationResults(), + candidate.getNote()); + } + + public Candidate toCandidate(CandidateDTO candidate){ + if(candidate == null){ + return new Candidate(); + } + + return new Candidate(candidate.getId(), + candidate.getScore(), + candidate.getSubjectIdentifier(), + candidate.getImageList(), + candidate.getAdjudicationResults(), + candidate.getNote()); + } + + public ImageDataDTO toImageDataDTO(ImageData imageData){ + if(imageData == null){ + return new ImageDataDTO(); + } + + List annotationDTOList = new ArrayList<>(); + + for(Annotation annotation : imageData.getAnnotations()){ + annotationDTOList.add(this.toAnnotationDTO(annotation)); + } + + return new ImageDataDTO(imageData.getId(), + imageData.getEbtsImageId(), + imageData.getUnrollImageId(), + imageData.getUnrollRingData(), + imageData.getRasterizeEbtsImageId(), + imageData.getEyeLabel(), + imageData.getContactType(), + annotationDTOList, + imageData.getAciiHorizontal(), + imageData.isReported(), + imageData.getImageNotes(), + imageData.getDme(), + imageData.getImageData(), + imageData.getUnrollImageData(), + imageData.getImageBytes(), + imageData.getUnrollImageBytes(), + imageData.getRecordType(), + imageData.getRecordIndex(), + imageData.getQualityScore()); + } + + public ImageData toImageData(ImageDataDTO imageData){ + if(imageData == null){ + return new ImageData(); + } + + List annotationList = new ArrayList<>(); + + for(AnnotationDTO annotation : imageData.getAnnotations()){ + annotationList.add(this.toAnnotation(annotation)); + } + + return new ImageData(imageData.getId(), + imageData.getEbtsImageId(), + imageData.getUnrollImageId(), + imageData.getUnrollRingData(), + imageData.getRasterizeEbtsImageId(), + imageData.getEyeLabel(), + imageData.getContactType(), + annotationList, + imageData.getAciiHorizontal(), + imageData.isReported(), + imageData.getImageNotes(), + imageData.getDme(), + imageData.getImageData(), + imageData.getUnrollImageData(), + imageData.getImageBytes(), + imageData.getUnrollImageBytes(), + imageData.getRecordType(), + imageData.getRecordIndex(), + imageData.getQualityScore()); + } + + public AnnotationDTO toAnnotationDTO(Annotation annotation){ + if(annotation == null){ + return new AnnotationDTO(); + } + + List annotationDTOList = new ArrayList<>(); + + for(Annotation child : annotation.getTempChildren()){ + annotationDTOList.add(this.toAnnotationDTO(child)); + } + + return new AnnotationDTO(annotation.getId(), + annotation.getText(), + annotation.getType(), + annotation.getCreationDateEpoch(), + this.toAnnotationValueDTO(annotation.getValue()), + annotationDTOList, + annotation.getParentId()); + } + + public Annotation toAnnotation(AnnotationDTO annotation){ + if(annotation == null){ + return new Annotation(); + } + + List annotationList = new ArrayList<>(); + + for(AnnotationDTO child : annotation.getTempChildren()){ + annotationList.add(this.toAnnotation(child)); + } + + return new Annotation(annotation.getId(), + annotation.getText(), + annotation.getType(), + annotation.getCreationDateEpoch(), + this.toAnnotationValue(annotation.getValue()), + annotationList, + annotation.getParentId()); + } + + public AnnotationValueDTO toAnnotationValueDTO(AnnotationValue annotationValue){ + if(annotationValue == null){ + return new AnnotationValueDTO(); + } + + return new AnnotationValueDTO(annotationValue.getId(), + annotationValue.getPoints(), + this.toAnnotationDTO(annotationValue.getAnnotation()), + annotationValue.getData()); + } + + public AnnotationValue toAnnotationValue(AnnotationValueDTO annotationValue){ + if(annotationValue == null){ + return new AnnotationValue(); + } + + return new AnnotationValue(annotationValue.getId(), + annotationValue.getPoints(), + this.toAnnotation(annotationValue.getAnnotation()), + annotationValue.getData()); + } + +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/PersonDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/PersonDTO.java new file mode 100644 index 0000000..7cd5de2 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/PersonDTO.java @@ -0,0 +1,103 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import java.util.Collection; +import java.util.ArrayList; + +public class PersonDTO { + public PersonDTO(){} + + public PersonDTO(Long id, String name, String title, String department, String address, String email, String phone, Collection cases) { + this.id = id; + this.name = name; + this.title = title; + this.department = department; + this.address = address; + this.email = email; + this.phone = phone; + this.cases = cases; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDepartment() { + return department; + } + + public void setDepartment(String department) { + this.department = department; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public Collection getCases() { + return cases; + } + + public void setCases(Collection cases) { + this.cases = cases; + } + + Long id; + String name; + String title; + String department; + String address; + String email; + String phone; + Collection cases = new ArrayList<>(); + +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/PrivilegeDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/PrivilegeDTO.java new file mode 100644 index 0000000..ea368c1 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/PrivilegeDTO.java @@ -0,0 +1,49 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +public class PrivilegeDTO { + public PrivilegeDTO(){} + + public PrivilegeDTO(Long id, String name, String description){ + this.id = id; + this.name = name; + this.description = description; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + Long id; + String name; + String description; +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/ProbeDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/ProbeDTO.java new file mode 100644 index 0000000..1a45090 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/ProbeDTO.java @@ -0,0 +1,85 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import org.mitre.iwp.web.model.ImageData; + +import java.util.ArrayList; +import java.util.List; + +public class ProbeDTO { + public ProbeDTO(){} + + public ProbeDTO(Long id, String transactionControlReference, String transactionControlNumber, List imageList, String name, String subjectIdentifier) { + this.id = id; + this.transactionControlReference = transactionControlReference; + this.transactionControlNumber = transactionControlNumber; + this.imageList = imageList; + this.name = name; + this.subjectIdentifier = subjectIdentifier; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTransactionControlReference() { + return transactionControlReference; + } + + public void setTransactionControlReference(String transactionControlReference) { + this.transactionControlReference = transactionControlReference; + } + + public String getTransactionControlNumber() { + return transactionControlNumber; + } + + public void setTransactionControlNumber(String transactionControlNumber) { + this.transactionControlNumber = transactionControlNumber; + } + + public List getImageList() { + return imageList; + } + + public void setImageList(List imageList) { + this.imageList = imageList; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSubjectIdentifier() { + return subjectIdentifier; + } + + public void setSubjectIdentifier(String subjectIdentifier) { + this.subjectIdentifier = subjectIdentifier; + } + + Long id; + String transactionControlReference; + String transactionControlNumber; + List imageList = new ArrayList<>(); + String name; + String subjectIdentifier; + +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/ProfileDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/ProfileDTO.java new file mode 100644 index 0000000..d10ee7b --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/ProfileDTO.java @@ -0,0 +1,161 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +public class ProfileDTO { + public ProfileDTO(){} + + public ProfileDTO(Long id, String name, String destinationAgencyIdentifier, String originatingAgencyIdentifier, String attentionIndicator, + String sourceAgency, boolean isReportExclude, String fullName, String email, String phoneNumber, String title, + String department, String address, UserSettingDTO userSetting){ + this.id = id; + this.name = name; + this.destinationAgencyIdentifier = destinationAgencyIdentifier; + this.originatingAgencyIdentifier = originatingAgencyIdentifier; + this.attentionIndicator = attentionIndicator; + this.sourceAgency = sourceAgency; + this.isReportExclude = isReportExclude; + this.fullName = fullName; + this.email = email; + this.phoneNumber = phoneNumber; + this.title = title; + this.department = department; + this.address = address; + this.userSetting = userSetting; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDestinationAgencyIdentifier() { + return destinationAgencyIdentifier; + } + + public void setDestinationAgencyIdentifier(String destinationAgencyIdentifier) { + this.destinationAgencyIdentifier = destinationAgencyIdentifier; + } + + public String getOriginatingAgencyIdentifier() { + return originatingAgencyIdentifier; + } + + public void setOriginatingAgencyIdentifier(String originatingAgencyIdentifier) { + this.originatingAgencyIdentifier = originatingAgencyIdentifier; + } + + public String getAttentionIndicator() { + return attentionIndicator; + } + + public void setAttentionIndicator(String attentionIndicator) { + this.attentionIndicator = attentionIndicator; + } + + public String getSourceAgency() { + return sourceAgency; + } + + public void setSourceAgency(String sourceAgency) { + this.sourceAgency = sourceAgency; + } + + public boolean getIsReportExclude() { + return isReportExclude; + } + + public void setReportExclude(boolean reportExclude) { + isReportExclude = reportExclude; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public void setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDepartment() { + return department; + } + + public void setDepartment(String department) { + this.department = department; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public UserSettingDTO getUserSetting() { + return userSetting; + } + + public void setUserSetting(UserSettingDTO userSetting) { + this.userSetting = userSetting; + } + + Long id; + String name; + String destinationAgencyIdentifier; + String originatingAgencyIdentifier; + String attentionIndicator; + String sourceAgency; + boolean isReportExclude; + String fullName; + String email; + String phoneNumber; + String title; + String department; + String address; + UserSettingDTO userSetting; +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/ReportDataDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/ReportDataDTO.java new file mode 100644 index 0000000..696dee9 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/ReportDataDTO.java @@ -0,0 +1,164 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import org.mitre.iwp.web.model.*; + +import java.util.ArrayList; +import java.util.List; + +public class ReportDataDTO { + public ReportDataDTO(){} + + public ReportDataDTO(Long id, String fileName, ProbeDTO probe, List candidates, boolean includeExclude, ResponseType responseType, CaseInformationDTO caseInformation, List results, List invalidFields, List errorMessages, UserAuthenticationDTO createdBy, UserAuthenticationDTO workedBy, Boolean isFinished, Boolean allowNullRequester) { + this.id = id; + this.fileName = fileName; + this.probe = probe; + this.candidates = candidates; + this.includeExclude = includeExclude; + this.responseType = responseType; + this.caseInformation = caseInformation; + this.results = results; + this.invalidFields = invalidFields; + this.errorMessages = errorMessages; + this.createdBy = createdBy; + this.workedBy = workedBy; + this.isFinished = isFinished; + this.allowNullRequester = allowNullRequester; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public ProbeDTO getProbe() { + return probe; + } + + public void setProbe(ProbeDTO probe) { + this.probe = probe; + } + + public List getCandidates() { + return candidates; + } + + public void setCandidates(List candidates) { + this.candidates = candidates; + } + + public boolean isIncludeExclude() { + return includeExclude; + } + + public void setIncludeExclude(boolean includeExclude) { + this.includeExclude = includeExclude; + } + + public ResponseType getResponseType() { + return responseType; + } + + public void setResponseType(ResponseType responseType) { + this.responseType = responseType; + } + + public CaseInformationDTO getCaseInformation() { + return caseInformation; + } + + public void setCaseInformation(CaseInformationDTO caseInformation) { + this.caseInformation = caseInformation; + } + + public List getResults() { + return results; + } + + public void setResults(List results) { + this.results = results; + } + + public List getInvalidFields() { + return invalidFields; + } + + public void setInvalidFields(List invalidFields) { + this.invalidFields = invalidFields; + } + + public List getErrorMessages() { + return errorMessages; + } + + public void setErrorMessages(List errorMessages) { + this.errorMessages = errorMessages; + } + + public UserAuthenticationDTO getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(UserAuthenticationDTO createdBy) { + this.createdBy = createdBy; + } + + public UserAuthenticationDTO getWorkedBy() { + return workedBy; + } + + public void setWorkedBy(UserAuthenticationDTO workedBy) { + this.workedBy = workedBy; + } + + public Boolean getIsFinished() { + return isFinished; + } + + public void setIsFinished(Boolean isFinished) { + this.isFinished = isFinished; + } + + public Boolean getAllowNullRequester() { + return allowNullRequester; } + + public void setAllowNullRequester(Boolean allowNullRequester) { + this.allowNullRequester = allowNullRequester +; + } + + Long id; + String fileName; + ProbeDTO probe = new ProbeDTO(); + List candidates = new ArrayList<>(); + boolean includeExclude; + ResponseType responseType; + CaseInformationDTO caseInformation = new CaseInformationDTO(); + List results = new ArrayList<>(); + List invalidFields = new ArrayList<>(); + List errorMessages = new ArrayList<>(); + UserAuthenticationDTO createdBy = new UserAuthenticationDTO(); + UserAuthenticationDTO workedBy = new UserAuthenticationDTO(); + Boolean isFinished = false; + Boolean allowNullRequester; +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/ReviewCaseDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/ReviewCaseDTO.java new file mode 100644 index 0000000..2e74c27 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/ReviewCaseDTO.java @@ -0,0 +1,132 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import java.util.Date; +import java.util.List; + +public class ReviewCaseDTO { + public ReviewCaseDTO(){} + + public ReviewCaseDTO(Long id, String title, String description, String priority, Date dueDate, Date createdDate, String createdBy, Date modifiedDate, String modifiedBy, List tags, boolean isWholeCaseCompleted) { + this.id = id; + this.title = title; + this.description = description; + this.priority = priority; + this.dueDate = dueDate; + this.createdDate = createdDate; + this.createdBy = createdBy; + this.modifiedDate = modifiedDate; + this.modifiedBy = modifiedBy; + this.tags = tags; + this.isWholeCaseCompleted = isWholeCaseCompleted; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getPriority() { + return priority; + } + + public void setPriority(String priority) { + this.priority = priority; + } + + public Date getDueDate() { + return dueDate; + } + + public void setDueDate(Date dueDate) { + this.dueDate = dueDate; + } + + public Date getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Date createdDate) { + this.createdDate = createdDate; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public Date getModifiedDate() { + return modifiedDate; + } + + public void setModifiedDate(Date modifiedDate) { + this.modifiedDate = modifiedDate; + } + + public String getModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(String modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } + + public boolean getIsWholeCaseCompleted() { + return isWholeCaseCompleted; + } + + public void setIsWholeCaseCompleted(boolean wholeCaseCompleted) { + isWholeCaseCompleted = wholeCaseCompleted; + } + + Long id; + String title; + String description; + String priority; + Date dueDate; + Date createdDate; + String createdBy; + Date modifiedDate; + String modifiedBy; + List tags; + boolean isWholeCaseCompleted; +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/UserAuthenticationDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/UserAuthenticationDTO.java new file mode 100644 index 0000000..3974813 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/UserAuthenticationDTO.java @@ -0,0 +1,82 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import java.util.List; + +public class UserAuthenticationDTO { + public UserAuthenticationDTO() {} + + public UserAuthenticationDTO(Long id, String username, String password, List roles, ProfileDTO profile, boolean isNewUser){ + this.id = id; + this.username = username; + this.password = password; + this.roles = roles; + this.profile = profile; + this.isNewUser = isNewUser; + } + + private Long id; + String username; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public ProfileDTO getProfile() { + return profile; + } + + public void setProfile(ProfileDTO profile) { + this.profile = profile; + } + + public boolean getIsNewUser() { + return isNewUser; + } + + public void setIsNewUser(boolean isNewUser) { + this.isNewUser = isNewUser; + } + + String password; + List roles; + ProfileDTO profile; + boolean isNewUser; +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/UserMapper.java b/web/src/main/java/org/mitre/iwp/web/dto/UserMapper.java new file mode 100644 index 0000000..a9d364d --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/UserMapper.java @@ -0,0 +1,164 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import org.mitre.iwp.web.model.*; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collection; + +@Component +public class UserMapper { + public AppNotificationDTO toAppNotificationDTO(AppNotification appNotification){ + if(appNotification == null){ + return new AppNotificationDTO(); + } + + return new AppNotificationDTO(appNotification.getId(), + appNotification.getUserId(), + appNotification.getMessage(), + appNotification.getIcon(), + appNotification.getAction(), + appNotification.getDateCreated(), + appNotification.getIsNew()); + } + + public AppNotification toAppNotification(AppNotificationDTO appNotification){ + if(appNotification == null){ + return new AppNotification(); + } + + return new AppNotification(appNotification.getId(), + appNotification.getUserId(), + appNotification.getMessage(), + appNotification.getIcon(), + appNotification.getAction(), + appNotification.getDateCreated(), + appNotification.isNew()); + } + + public UserAuthenticationDTO toUserAuthenticationDTO(UserAuthentication userAuthentication){ + if(userAuthentication == null){ + return null; + } + + ProfileDTO profileDto = this.toProfileDto(userAuthentication.getProfile()); + + return new UserAuthenticationDTO(userAuthentication.getId(), userAuthentication.getUsername(), userAuthentication.getPassword(), + userAuthentication.getRoles(), profileDto, true); + } + + public UserAuthentication toUserAuthentication(UserAuthenticationDTO userAuthenticationDTO){ + if(userAuthenticationDTO == null){ + return null; + } + + Profile profile = this.toProfile(userAuthenticationDTO.profile); + + return new UserAuthentication(userAuthenticationDTO.getId(), userAuthenticationDTO.username, userAuthenticationDTO.password, + userAuthenticationDTO.roles, profile, userAuthenticationDTO.isNewUser); + } + + public ProfileDTO toProfileDto(Profile profile){ + if(profile == null){ + return new ProfileDTO(); + } + + UserSettingDTO userSettingDto = this.toUserSettingDto(profile.getUserSetting()); + return new ProfileDTO(profile.getId(), profile.getName(), profile.getDestinationAgencyIdentifier(), + profile.getOriginatingAgencyIdentifier(), profile.getAttentionIndicator(), profile.getSourceAgency(), + profile.getIsReportExclude(), profile.getFullName(), profile.getEmail(), profile.getPhoneNumber(), + profile.getTitle(), profile.getDepartment(), profile.getAddress(), userSettingDto); + } + + public Profile toProfile(ProfileDTO profileDto){ + if(profileDto == null){ + return new Profile(); + } + + UserSetting userSetting = this.toUserSetting(profileDto.userSetting); + return new Profile(profileDto.getId(), profileDto.getName(), profileDto.getDestinationAgencyIdentifier(), + profileDto.getOriginatingAgencyIdentifier(), profileDto.getAttentionIndicator(), profileDto.getSourceAgency(), + profileDto.getIsReportExclude(), profileDto.getFullName(), profileDto.getEmail(), profileDto.getPhoneNumber(), + profileDto.getTitle(), profileDto.getDepartment(), profileDto.getAddress(),userSetting); + } + + public UserSettingDTO toUserSettingDto(UserSetting userSetting){ + if(userSetting == null){ + return new UserSettingDTO(); + } + + return new UserSettingDTO(userSetting.getId(), userSetting.getStrokeWidth(), userSetting.getDrawColor(), + userSetting.getFillColor(), userSetting.getNumHistory(), userSetting.getTshepiiStrokeOpacity()); + } + + public UserSetting toUserSetting(UserSettingDTO userSettingDto){ + if(userSettingDto == null){ + return new UserSetting(); + } + + return new UserSetting(userSettingDto.getId(), userSettingDto.getStrokeWidth(), userSettingDto.getDrawColor(), + userSettingDto.getFillColor(), userSettingDto.getNumHistory(), userSettingDto.getTshepiiStrokeOpacity()); + } + + public UserRoleDTO toUserRoleDto(UserRole userRole){ + if(userRole == null){ + return new UserRoleDTO(); + } + + Collection privilegeDTOCollection = new ArrayList<>(); + for(Privilege p : userRole.getPrivileges()){ + privilegeDTOCollection.add(this.toPrivilegeDTO(p)); + } + + Collection userAuthenticationDTOCollection = new ArrayList<>(); + for(UserAuthentication ua : userRole.getUsers()){ + userAuthenticationDTOCollection.add(this.toUserAuthenticationDTO(ua)); + } + + return new UserRoleDTO(userRole.getId(), userRole.getName(), userRole.getDescription(), userAuthenticationDTOCollection, privilegeDTOCollection); + } + + public UserRole toUserRole(UserRoleDTO userRoleDTO){ + if(userRoleDTO == null){ + return new UserRole(); + } + + Collection privilegeCollection = new ArrayList<>(); + for(PrivilegeDTO p : userRoleDTO.getPrivileges()){ + privilegeCollection.add(this.toPrivilege(p)); + } + + Collection userAuthenticationCollection = new ArrayList<>(); + for(UserAuthenticationDTO ua : userRoleDTO.getUsers()){ + userAuthenticationCollection.add(this.toUserAuthentication(ua)); + } + + return new UserRole(userRoleDTO.getId(), userRoleDTO.getName(), userRoleDTO.getDescription(), userAuthenticationCollection, privilegeCollection); + } + + public PrivilegeDTO toPrivilegeDTO(Privilege privilege){ + if(privilege == null){ + return new PrivilegeDTO(); + } + + return new PrivilegeDTO(privilege.getId(), privilege.getName(), privilege.getDescription()); + } + + public Privilege toPrivilege(PrivilegeDTO privilegeDto){ + if(privilegeDto == null){ + return new Privilege(); + } + + return new Privilege(privilegeDto.getId(), privilegeDto.getName(), privilegeDto.getDescription()); + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/UserRoleDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/UserRoleDTO.java new file mode 100644 index 0000000..5a0e03a --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/UserRoleDTO.java @@ -0,0 +1,71 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +import java.util.Collection; + +public class UserRoleDTO { + public UserRoleDTO(){} + + public UserRoleDTO(Long id, String name, String description, Collection users, Collection privileges){ + this.id = id; + this.name = name; + this.description = description; + this.users = users; + this.privileges = privileges; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Collection getUsers() { + return users; + } + + public void setUsers(Collection users) { + this.users = users; + } + + public Collection getPrivileges() { + return privileges; + } + + public void setPrivileges(Collection privileges) { + this.privileges = privileges; + } + + Long id; + String name; + String description; + Collection users; + Collection privileges; +} diff --git a/web/src/main/java/org/mitre/iwp/web/dto/UserSettingDTO.java b/web/src/main/java/org/mitre/iwp/web/dto/UserSettingDTO.java new file mode 100644 index 0000000..8711a5c --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/dto/UserSettingDTO.java @@ -0,0 +1,79 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.dto; + +public class UserSettingDTO { + public UserSettingDTO(){} + + public UserSettingDTO(Long id, int strokeWidth, String drawColor, String fillColor, int numHistory, int tshepiiStrokeOpacity){ + this.id = id; + this.strokeWidth = strokeWidth; + this.drawColor = drawColor; + this.fillColor = fillColor; + this.numHistory = numHistory; + this.tshepiiStrokeOpacity = tshepiiStrokeOpacity; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public int getStrokeWidth() { + return strokeWidth; + } + + public void setStrokeWidth(int strokeWidth) { + this.strokeWidth = strokeWidth; + } + + public String getDrawColor() { + return drawColor; + } + + public void setDrawColor(String drawColor) { + this.drawColor = drawColor; + } + + public String getFillColor() { + return fillColor; + } + + public void setFillColor(String fillColor) { + this.fillColor = fillColor; + } + + public int getNumHistory() { + return numHistory; + } + + public void setNumHistory(int numHistory) { + this.numHistory = numHistory; + } + + public int getTshepiiStrokeOpacity() { + return tshepiiStrokeOpacity; + } + + public void setTshepiiStrokeOpacity(int tshepiiStrokeOpacity) { + this.tshepiiStrokeOpacity = tshepiiStrokeOpacity; + } + + Long id; + int strokeWidth; + String drawColor; + String fillColor; + int numHistory; + int tshepiiStrokeOpacity; +} diff --git a/web/src/main/java/org/mitre/iwp/web/exception/InvalidMessageException.java b/web/src/main/java/org/mitre/iwp/web/exception/InvalidMessageException.java new file mode 100644 index 0000000..954f6b3 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/exception/InvalidMessageException.java @@ -0,0 +1,33 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.exception; + +public class InvalidMessageException extends Exception { + public InvalidMessageException() { + super(); + } + + public InvalidMessageException(String message) { + super(message); + } + + public InvalidMessageException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidMessageException(Throwable cause) { + super(cause); + } + + protected InvalidMessageException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/jms/AciiReceiver.java b/web/src/main/java/org/mitre/iwp/web/jms/AciiReceiver.java new file mode 100644 index 0000000..17108d2 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/jms/AciiReceiver.java @@ -0,0 +1,67 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.jms; + +import com.google.protobuf.InvalidProtocolBufferException; +import org.mitre.iwp.buffers.Acii.AciiServiceResponse; +import org.mitre.iwp.web.model.AciiResponse; +import org.mitre.iwp.web.model.ServiceCallStatus; +import org.mitre.iwp.web.service.AciiResponseRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.stereotype.Component; + +import jakarta.jms.BytesMessage; +import jakarta.jms.JMSException; + +@Component +@ConditionalOnProperty(prefix = "iris", name = "aciiService", havingValue = "true") +public class AciiReceiver { + private static final Logger log = LoggerFactory.getLogger(AciiReceiver.class); + private final AciiResponseRepository aciiResponseRepository; + + @Autowired + public AciiReceiver( + AciiResponseRepository aciiResponseRepository) { + this.aciiResponseRepository = aciiResponseRepository; + } + + @JmsListener(destination = "#{iwpWebProperties.getAciiQueueResponse()}") + public void processMessage(Object object) { + try { + BytesMessage bMessage = (BytesMessage) object; + + byte[] bytes = new byte[(int) bMessage.getBodyLength()]; + bMessage.readBytes(bytes); + AciiServiceResponse serviceResponse = AciiServiceResponse.parseFrom(bytes); + Long responseImageId = Long.parseLong(serviceResponse.getImageId()); + + log.info("Acii Response for Image Id: {}", responseImageId); + AciiResponse aciiResponse = this.aciiResponseRepository.findByImageId(responseImageId); + + if(aciiResponse == null){ + aciiResponse = new AciiResponse(); + } + + aciiResponse.setHorizontal(serviceResponse.getHorizontal().toString()); + aciiResponse.setVertical(serviceResponse.getVertical().toString()); + aciiResponse.setStatus(ServiceCallStatus.Received); + + this.aciiResponseRepository.save(aciiResponse); + } catch (JMSException | InvalidProtocolBufferException jmsException) { + log.debug(jmsException.getMessage()); + + } + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/jms/BiqtReceiver.java b/web/src/main/java/org/mitre/iwp/web/jms/BiqtReceiver.java new file mode 100644 index 0000000..e34e20d --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/jms/BiqtReceiver.java @@ -0,0 +1,69 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.jms; + +import com.google.protobuf.InvalidProtocolBufferException; +import org.mitre.iwp.buffers.Biqt; +import org.mitre.iwp.web.model.BiqtResponse; +import org.mitre.iwp.web.model.ServiceCallStatus; +import org.mitre.iwp.web.service.BiqtResponseRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.stereotype.Component; + +import jakarta.jms.BytesMessage; +import jakarta.jms.JMSException; + +@Component +@ConditionalOnProperty(prefix = "iris", name = "biqtService", havingValue = "true") +public class BiqtReceiver { + private static final Logger log = LoggerFactory.getLogger(BiqtReceiver.class); + private final BiqtResponseRepository biqtResponseRepository; + + @Autowired + public BiqtReceiver(BiqtResponseRepository biqtResponseRepository) { + this.biqtResponseRepository = biqtResponseRepository; + } + + @JmsListener(destination = "#{iwpWebProperties.getBiqtQueueResponse()}") + public void processMessage(Object object) { + try { + BytesMessage bMessage = (BytesMessage) object; + + byte[] bytes = new byte[(int) bMessage.getBodyLength()]; + bMessage.readBytes(bytes); + Biqt.BiqtResponse serviceResponse = Biqt.BiqtResponse.parseFrom(bytes); + Long responseImageId = Long.parseLong(serviceResponse.getImageId()); + + log.info("Biqt Response for Image Id: {}", responseImageId); + BiqtResponse biqtResponse = this.biqtResponseRepository.findByImageId(responseImageId); + + if(biqtResponse == null){ + biqtResponse = new BiqtResponse(); + } + + biqtResponse.setIrisCenterX(serviceResponse.getIrisCenterX()); + biqtResponse.setIrisCenterY(serviceResponse.getIrisCenterY()); + biqtResponse.setQuality(serviceResponse.getQuality()); + biqtResponse.setPercentVisibleIris(serviceResponse.getPercentVisibleIris()); + biqtResponse.setIrisDiameter(serviceResponse.getIrisDiameter()); + biqtResponse.setStatus(ServiceCallStatus.Received); + + this.biqtResponseRepository.save(biqtResponse); + } catch (JMSException | InvalidProtocolBufferException jmsException) { + log.debug(jmsException.getMessage()); + + } + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/jms/ContactClassifierReceiver.java b/web/src/main/java/org/mitre/iwp/web/jms/ContactClassifierReceiver.java new file mode 100644 index 0000000..59fadeb --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/jms/ContactClassifierReceiver.java @@ -0,0 +1,66 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.jms; + +import com.google.protobuf.InvalidProtocolBufferException; +import org.mitre.iwp.buffers.ContactClassifier; +import org.mitre.iwp.web.model.ContactClassifierResponse; +import org.mitre.iwp.web.model.ServiceCallStatus; +import org.mitre.iwp.web.service.ContactClassifierResponseRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.stereotype.Component; + +import jakarta.jms.BytesMessage; +import jakarta.jms.JMSException; + +@Component +@ConditionalOnProperty(prefix = "iris", name = "contactService", havingValue = "true") +public class ContactClassifierReceiver { + private static final Logger log = LoggerFactory.getLogger(ContactClassifierReceiver.class); + private final ContactClassifierResponseRepository contactClassifierResponseRepository; + + @Autowired + public ContactClassifierReceiver(ContactClassifierResponseRepository contactClassifierResponseRepository) { + this.contactClassifierResponseRepository = contactClassifierResponseRepository; + } + + @JmsListener(destination = "#{iwpWebProperties.getContactQueueResponse()}") + public void processMessage(Object object) { + try { + BytesMessage bMessage = (BytesMessage) object; + + byte[] bytes = new byte[(int) bMessage.getBodyLength()]; + bMessage.readBytes(bytes); + ContactClassifier.ContactClassifierResponse serviceResponse = ContactClassifier.ContactClassifierResponse.parseFrom(bytes); + Long responseImageId = Long.parseLong(serviceResponse.getImageId()); + + log.info("ContactClassifier Response for Image Id: {}", responseImageId); + ContactClassifierResponse contactResponse = this.contactClassifierResponseRepository.findByImageId(responseImageId); + + if(contactResponse == null){ + contactResponse = new ContactClassifierResponse(); + } + + contactResponse.setDetectionCode(serviceResponse.getDetectionCode()); + contactResponse.setProcessTimeMS(serviceResponse.getProcessTimeMS()); + contactResponse.setStatus(ServiceCallStatus.Received); + + this.contactClassifierResponseRepository.save(contactResponse); + } catch (JMSException | InvalidProtocolBufferException jmsException) { + log.debug(jmsException.getMessage()); + + } + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/jms/DualPDMReceiver.java b/web/src/main/java/org/mitre/iwp/web/jms/DualPDMReceiver.java new file mode 100644 index 0000000..4516ab9 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/jms/DualPDMReceiver.java @@ -0,0 +1,115 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.jms; + +import org.mitre.iwp.buffers.Pdm; +import org.mitre.iwp.web.model.DualPDMCompareResponse; +import org.mitre.iwp.web.model.DualPDMResponse; +import org.mitre.iwp.web.model.ServiceCallStatus; +import org.mitre.iwp.web.service.DualPDMCompareResponseRepository; +import org.mitre.iwp.web.service.DualPDMResponseRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.stereotype.Component; + +import com.google.protobuf.InvalidProtocolBufferException; + +import jakarta.jms.BytesMessage; +import jakarta.jms.JMSException; + +@Component +@ConditionalOnProperty(prefix = "iris", name = "dualPDMService", havingValue = "true") +public class DualPDMReceiver { + private static final Logger log = LoggerFactory.getLogger(DualPDMReceiver.class); + private final DualPDMResponseRepository dualPDMResponseRepository; + private final DualPDMCompareResponseRepository dualPDMCompareResponseRepository; + + @Autowired + public DualPDMReceiver( + DualPDMResponseRepository dualPDMResponseRepository, + DualPDMCompareResponseRepository dualPDMCompareResponseRepository) { + this.dualPDMResponseRepository = dualPDMResponseRepository; + this.dualPDMCompareResponseRepository = dualPDMCompareResponseRepository; + } + + @JmsListener(destination = "#{iwpWebProperties.getDualPDMQueueResponse()}") + public void processMessage(BytesMessage bMessage) { + try { + log.info("Received Response from Iwp-pdm DUAL PDM"); + + Long probeId = bMessage.getLongProperty("probeId"); + Long candidateId = bMessage.getLongProperty("candidateId"); + + // Parse the incoming message + byte[] bytes = new byte[(int) bMessage.getBodyLength()]; + bMessage.readBytes(bytes); + + + // Attempt to parse the message as DualPDMComparison + Pdm.DualPDMComparison comparison = this.tryParseComparison(bytes); + if (comparison == null) { + log.error("Failed to parse DualPDMComparison message."); + return; + } + + // Extract probe and candidate images + byte[] probeBytes = comparison.getProbe().toByteArray(); + byte[] candidateBytes = comparison.getCandidate().toByteArray(); + + log.info("DualPDM Response for Probe Id: {} and Candidate Id: {}", probeId, candidateId); + + DualPDMResponse probeResponse = dualPDMResponseRepository.findByImageId(probeId); + if (probeResponse == null) { + probeResponse = new DualPDMResponse(); + } + probeResponse.setImageBytes(probeBytes); + probeResponse.setStatus(ServiceCallStatus.Received); + dualPDMResponseRepository.save(probeResponse); + + DualPDMResponse candidateResponse = dualPDMResponseRepository.findByImageId(candidateId); + if (candidateResponse == null) { + candidateResponse = new DualPDMResponse(); + } + candidateResponse.setImageBytes(candidateBytes); + candidateResponse.setStatus(ServiceCallStatus.Received); + dualPDMResponseRepository.save(candidateResponse); + + // Save the combined comparison response + DualPDMCompareResponse compareResponse = dualPDMCompareResponseRepository.findByProbeIdAndCandidateId(probeId, candidateId); + if (compareResponse == null) { + compareResponse = new DualPDMCompareResponse(); + compareResponse.setProbeId(probeId); + compareResponse.setCandidateId(candidateId); + } + compareResponse.setServiceResponse(bytes); + compareResponse.setStatus(ServiceCallStatus.Received); + dualPDMCompareResponseRepository.save(compareResponse); + + } catch (JMSException ex) { + log.error("Error processing DualPDM message: {}", ex.getMessage(), ex); + } + } + + /** + * Attempt to parse the incoming bytes as a DualPDMComparison message. + */ + private Pdm.DualPDMComparison tryParseComparison(byte[] bytes) { + try { + return Pdm.DualPDMComparison.parseFrom(bytes); + } catch (InvalidProtocolBufferException ex) { + log.error("Failed to parse DualPDMComparison message: {}", ex.getMessage()); + return null; + } + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/jms/IrisAnnotationReceiver.java b/web/src/main/java/org/mitre/iwp/web/jms/IrisAnnotationReceiver.java new file mode 100644 index 0000000..ca56895 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/jms/IrisAnnotationReceiver.java @@ -0,0 +1,67 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.jms; + +import com.google.protobuf.InvalidProtocolBufferException; +import org.mitre.iwp.buffers.IrisAnnotation.*; +import org.mitre.iwp.web.IwpWebProperties; +import org.mitre.iwp.web.model.IrisAnnotationResponse; +import org.mitre.iwp.web.model.ServiceCallStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.stereotype.Component; +import org.mitre.iwp.web.service.IrisAnnotationResponseRepository; + +import jakarta.jms.BytesMessage; +import jakarta.jms.JMSException; + +@Component +@ConditionalOnProperty(prefix = "iris", name = "irisAnnotationService", havingValue = "true") +public class IrisAnnotationReceiver { + private static final Logger log = LoggerFactory.getLogger(IrisAnnotationReceiver.class); + private final IrisAnnotationResponseRepository irisAnnotationResponseRepository; + + @Autowired + public IrisAnnotationReceiver( + IwpWebProperties iwpWebProperties, + IrisAnnotationResponseRepository irisAnnotationResponseRepository){ + this.irisAnnotationResponseRepository = irisAnnotationResponseRepository; + } + + @JmsListener(destination = "#{iwpWebProperties.getIrisAnnotationQueueResponse()}") + public void processMessage(Object object) { + try { + BytesMessage bMessage = (BytesMessage) object; + + byte[] bytes = new byte[(int) bMessage.getBodyLength()]; + bMessage.readBytes(bytes); + IrisAnnotationReply serviceResponse = IrisAnnotationReply.parseFrom(bytes); + Long responseImageId = Long.parseLong(serviceResponse.getId()); + + log.info("Iris Annotation Response for Image Id: {}", responseImageId); + IrisAnnotationResponse irisAnnotationResponse = this.irisAnnotationResponseRepository.findByImageId(responseImageId); + + if(irisAnnotationResponse == null){ + irisAnnotationResponse = new IrisAnnotationResponse(); + } + + irisAnnotationResponse.setServiceResponse(bytes); + irisAnnotationResponse.setStatus(ServiceCallStatus.Received); + + this.irisAnnotationResponseRepository.save(irisAnnotationResponse); + } catch (JMSException | InvalidProtocolBufferException jmsException) { + log.debug(jmsException.getMessage()); + } + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/jms/PdmReceiver.java b/web/src/main/java/org/mitre/iwp/web/jms/PdmReceiver.java new file mode 100644 index 0000000..a5d3778 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/jms/PdmReceiver.java @@ -0,0 +1,99 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.jms; + +import com.google.protobuf.*; + +import java.util.Map; +import org.mitre.iwp.buffers.Pdm.DeformedImage; +import org.mitre.iwp.buffers.Pdm.DeformerWrapper; +import org.mitre.iwp.web.model.*; +import org.mitre.iwp.web.service.PdmDeformedImageResponseRepository; +import org.mitre.iwp.web.service.PdmResponseRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.stereotype.Component; + +import jakarta.jms.BytesMessage; +import jakarta.jms.JMSException; + +@Component +@ConditionalOnProperty(prefix = "iris", name = "pdmService", havingValue = "true") +public class PdmReceiver { + private static final Logger log = LoggerFactory.getLogger(PdmReceiver.class); + private final PdmResponseRepository pdmResponseRepository; + private final PdmDeformedImageResponseRepository pdmDeformedImageResponseRepository; + + @Autowired + public PdmReceiver(PdmResponseRepository pdmResponseRepository, PdmDeformedImageResponseRepository pdmDeformedImageResponseRepository) { + this.pdmResponseRepository = pdmResponseRepository; + this.pdmDeformedImageResponseRepository = pdmDeformedImageResponseRepository; + } + + @JmsListener(destination = "#{iwpWebProperties.getPdmQueueResponse()}") + public void processMessage(BytesMessage message) { + try { + // Read byte array from bytemessage + byte[] bytes = new byte[(int) message.getBodyLength()]; + message.readBytes(bytes); + + // Deserialize + DeformerWrapper serviceResponse = DeformerWrapper.parseFrom(bytes); + Map eyePreserveMap = serviceResponse.getEyePreserveResponseMap(); + String imageIdStr = serviceResponse.getImageId(); + + // Process imageId + Long responseImgId = Long.parseLong(imageIdStr); + log.info("Pdm Response for Image Id: {}", responseImgId); + + // Retrieve/Create new PdmResponse entity + PdmResponse pdmResponse = this.pdmResponseRepository.findByImageId(responseImgId); + + if (pdmResponse == null) { + pdmResponse = new PdmResponse(); + } + + // Convert and save each deformed image + for (Map.Entry entry : eyePreserveMap.entrySet()) { + PdmDeformedImageResponse pdmDeformedImageResponse = new PdmDeformedImageResponse(); + String alphaScore = entry.getKey(); + DeformedImage image = entry.getValue(); + + pdmDeformedImageResponse = convertToPdmDeformedImageResponse(pdmDeformedImageResponse, alphaScore, image, responseImgId); + this.pdmDeformedImageResponseRepository.save(pdmDeformedImageResponse); + + + // Set status and save + pdmResponse.setStatus(ServiceCallStatus.Received); + this.pdmResponseRepository.save(pdmResponse); + + } + } catch (JMSException | InvalidProtocolBufferException e) { + log.error("Error processing message: ", e); + } catch (NumberFormatException e) { + log.error("Invalid imageId format: ", e); + } + } + + private PdmDeformedImageResponse convertToPdmDeformedImageResponse(PdmDeformedImageResponse pdmDeformedeImageResponse, String alphaScore, DeformedImage protoDeformedImage, Long imageid){ + pdmDeformedeImageResponse.setDeformedId(imageid, alphaScore); + pdmDeformedeImageResponse.setImageData(protoDeformedImage.getImageData().toByteArray()); + pdmDeformedeImageResponse.setScore(protoDeformedImage.getScore()); + pdmDeformedeImageResponse.setImageType(protoDeformedImage.getImageType().name()); + pdmDeformedeImageResponse.setWidth(protoDeformedImage.getWidth()); + pdmDeformedeImageResponse.setHeight(protoDeformedImage.getHeight()); + return pdmDeformedeImageResponse; + + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/jms/ServiceMessageHandler.java b/web/src/main/java/org/mitre/iwp/web/jms/ServiceMessageHandler.java new file mode 100644 index 0000000..f0f0e0d --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/jms/ServiceMessageHandler.java @@ -0,0 +1,480 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.jms; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; + +import org.mitre.iwp.buffers.Iwp; +import org.mitre.iwp.buffers.Pdm; +import org.mitre.iwp.buffers.Tshepii; +import org.mitre.iwp.web.IwpWebProperties; +import org.mitre.iwp.web.data.EbtsImage; +import org.mitre.iwp.web.model.AciiResponse; +import org.mitre.iwp.web.model.BiqtResponse; +import org.mitre.iwp.web.model.ServiceCallStatus; +import org.mitre.iwp.web.model.ContactClassifierResponse; +import org.mitre.iwp.web.model.TshepiiResponse; +import org.mitre.iwp.web.model.TshepiiCompareResponse; +import org.mitre.iwp.web.model.IrisAnnotationResponse; +import org.mitre.iwp.web.model.PdmResponse; +import org.mitre.iwp.web.model.DualPDMResponse; +import org.mitre.iwp.web.model.DualPDMCompareResponse; +import org.mitre.iwp.web.service.AciiResponseRepository; +import org.mitre.iwp.web.service.BiqtResponseRepository; +import org.mitre.iwp.web.service.ContactClassifierResponseRepository; +import org.mitre.iwp.web.service.DualPDMResponseRepository; +import org.mitre.iwp.web.service.DualPDMCompareResponseRepository; +import org.mitre.iwp.web.service.TshepiiResponseRepository; +import org.mitre.iwp.web.service.TshepiiCompareResponseRepository; +import org.mitre.iwp.web.service.IrisAnnotationResponseRepository; +import org.mitre.iwp.web.service.PdmResponseRepository; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jms.core.JmsTemplate; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@Service +public class ServiceMessageHandler { + private static final Logger log = LoggerFactory.getLogger(ServiceMessageHandler.class); + + private final IwpWebProperties iwpWebProperties; + private final JmsTemplate jmsTemplate; + + private final AciiResponseRepository aciiResponseRepository; + private final BiqtResponseRepository biqtResponseRepository; + private final ContactClassifierResponseRepository contactClassifierResponseRepository; + private final DualPDMResponseRepository dualPDMResponseRepository; + private final DualPDMCompareResponseRepository dualPDMCompareResponseRepository; + private final TshepiiResponseRepository tshepiiResponseRepository; + private final TshepiiCompareResponseRepository tshepiiCompareResponseRepository; + private final IrisAnnotationResponseRepository irisAnnotationResponseRepository; + private final PdmResponseRepository pdmResponseRepository; + + @Autowired + public ServiceMessageHandler(IwpWebProperties iwpWebProperties, JmsTemplate jmsTemplate, + AciiResponseRepository aciiResponseRepository, BiqtResponseRepository biqtResponseRepository, + ContactClassifierResponseRepository contactClassifierResponseRepository, + DualPDMResponseRepository dualPDMResponseRepository, + DualPDMCompareResponseRepository dualPDMCompareResponseRepository, + TshepiiResponseRepository tshepiiResponseRepository, + TshepiiCompareResponseRepository tshepiiCompareResponseRepository, + IrisAnnotationResponseRepository irisAnnotationResponseRepository, + PdmResponseRepository pdmResponseRepository) { + this.iwpWebProperties = iwpWebProperties; + this.jmsTemplate = jmsTemplate; + this.aciiResponseRepository = aciiResponseRepository; + this.biqtResponseRepository = biqtResponseRepository; + this.contactClassifierResponseRepository = contactClassifierResponseRepository; + this.dualPDMResponseRepository = dualPDMResponseRepository; + this.dualPDMCompareResponseRepository = dualPDMCompareResponseRepository; + this.tshepiiResponseRepository = tshepiiResponseRepository; + this.tshepiiCompareResponseRepository = tshepiiCompareResponseRepository; + this.irisAnnotationResponseRepository = irisAnnotationResponseRepository; + this.pdmResponseRepository = pdmResponseRepository; + } + + public List getServicesStatus(){ + List serviceList = new ArrayList<>(); + + List excludedServices = this.iwpWebProperties.getExcludedServices(); + + // Expand as services are expanded + + org.mitre.iwp.web.model.Service irisAnnotation = new org.mitre.iwp.web.model.Service(); + irisAnnotation.setServiceEnabled(this.iwpWebProperties.getIrisAnnotationService()); + irisAnnotation.setServiceName(this.iwpWebProperties.getIrisAnnotationServiceName()); + irisAnnotation.setRequestQueue(this.iwpWebProperties.getIrisAnnotationQueueRequest()); + irisAnnotation.setResponseQueue(this.iwpWebProperties.getIrisAnnotationQueueResponse()); + irisAnnotation.setServiceExcluded(excludedServices.contains("annotation")); + serviceList.add(irisAnnotation); + + org.mitre.iwp.web.model.Service tshepii = new org.mitre.iwp.web.model.Service(); + tshepii.setServiceEnabled(this.iwpWebProperties.getTshepiiService()); + tshepii.setServiceName(this.iwpWebProperties.getTshepiiServiceName()); + tshepii.setRequestQueue(this.iwpWebProperties.getTshepiiQueueRequest()); + tshepii.setResponseQueue(this.iwpWebProperties.getTshepiiQueueResponse()); + tshepii.setServiceExcluded(excludedServices.contains("tshepii")); + serviceList.add(tshepii); + + org.mitre.iwp.web.model.Service acii = new org.mitre.iwp.web.model.Service(); + acii.setServiceEnabled(this.iwpWebProperties.getAciiService()); + acii.setServiceName(this.iwpWebProperties.getAciiServiceName()); + acii.setRequestQueue(this.iwpWebProperties.getAciiQueueRequest()); + acii.setResponseQueue(this.iwpWebProperties.getAciiQueueResponse()); + acii.setServiceExcluded(excludedServices.contains("acii")); + serviceList.add(acii); + + org.mitre.iwp.web.model.Service biqt = new org.mitre.iwp.web.model.Service(); + biqt.setServiceEnabled(this.iwpWebProperties.getBiqtService()); + biqt.setServiceName(this.iwpWebProperties.getBiqtServiceName()); + biqt.setRequestQueue(this.iwpWebProperties.getBiqtQueueRequest()); + biqt.setResponseQueue(this.iwpWebProperties.getBiqtQueueResponse()); + biqt.setServiceExcluded(excludedServices.contains("biqt")); + serviceList.add(biqt); + + org.mitre.iwp.web.model.Service pdm = new org.mitre.iwp.web.model.Service(); + pdm.setServiceEnabled(this.iwpWebProperties.getPdmService()); + pdm.setServiceName(this.iwpWebProperties.getPdmServiceName()); + pdm.setRequestQueue(this.iwpWebProperties.getPdmQueueRequest()); + pdm.setResponseQueue(this.iwpWebProperties.getPdmQueueResponse()); + pdm.setServiceExcluded(excludedServices.contains("pdm")); + serviceList.add(pdm); + + org.mitre.iwp.web.model.Service dualPDM = new org.mitre.iwp.web.model.Service(); + dualPDM.setServiceEnabled(this.iwpWebProperties.getDualPDMService()); + dualPDM.setServiceName(this.iwpWebProperties.getDualPDMServiceName()); + dualPDM.setRequestQueue(this.iwpWebProperties.getDualPDMQueueRequest()); + dualPDM.setResponseQueue(this.iwpWebProperties.getDualPDMQueueResponse()); + dualPDM.setServiceExcluded(excludedServices.contains("dualpdm")); + serviceList.add(dualPDM); + + return serviceList; + } + + public void processImageDefault(EbtsImage eImage) { + if (this.iwpWebProperties.getAciiService()) { + this.processAcii(eImage, null); + } + + if (this.iwpWebProperties.getBiqtService()) { + this.processBiqtContact(eImage, null); + } + + if (this.iwpWebProperties.getTshepiiService()) { + this.processTshepii(eImage, null); + } + + if (this.iwpWebProperties.getIrisAnnotationService()) { + this.processIrisAnnotation(eImage, null); + } + + if (this.iwpWebProperties.getPdmService()) { + this.processPdm(eImage, null); + } + + if (this.iwpWebProperties.getDualPDMService()) { + this.processDualPDM(eImage); + } + } + + public Long processAcii(EbtsImage eImage, Long ebtsImageId) { + AciiResponse response = this.aciiResponseRepository.findByImageId(eImage.getId()); + if (response != null) { + if (response.getStatus() != ServiceCallStatus.NotSent) { + return response.getImageId(); + } + } else { + response = new AciiResponse(); + if (ebtsImageId != null) { + response.setEbtsImageId(ebtsImageId); + } + } + + response.setStatus(ServiceCallStatus.Pending); + response = this.aciiResponseRepository.save(response); + + try { + this.pushMessageToQueue(this.iwpWebProperties.getAciiQueueRequest(), + ByteString.copyFrom(EbtsImage.getImageBytes(eImage)), response.getImageId()); + } catch (IOException ex) { + + response.setError(ex.getMessage()); + this.aciiResponseRepository.save(response); + } + return response.getImageId(); + } + + public Long processBiqtContact(EbtsImage eImage, Long ebtsImageId) { + BiqtResponse biqtResponse = this.processBiqtService(eImage, ebtsImageId); + ContactClassifierResponse contactClassifierResponse = this.processContactService(eImage, ebtsImageId); + + // BIQT & Contact's imageId should be the same since you can't send an image back to one service without sending it back to the other. + Long responseImageId = biqtResponse.getImageId(); + + try { + this.pushMessageToQueue(this.iwpWebProperties.getBiqtQueueRequest(), + ByteString.copyFrom(EbtsImage.getImageBytes(eImage)), responseImageId); + } catch (IOException ex) { + + biqtResponse.setError(ex.getMessage()); + this.biqtResponseRepository.save(biqtResponse); + contactClassifierResponse.setError(ex.getMessage()); + this.contactClassifierResponseRepository.save(contactClassifierResponse); + } + + return responseImageId; + } + + private BiqtResponse processBiqtService(EbtsImage eImage, Long ebtsImageId) { + BiqtResponse response = this.biqtResponseRepository.findByImageId(eImage.getId()); + if (response != null) { + if (response.getStatus() != ServiceCallStatus.NotSent) { + return response; + } + } else { + response = new BiqtResponse(); + if (ebtsImageId != null) { + response.setEbtsImageId(ebtsImageId); + } + } + + response.setStatus(ServiceCallStatus.Pending); + response = this.biqtResponseRepository.save(response); + + return response; + } + + private ContactClassifierResponse processContactService(EbtsImage eImage, Long ebtsImageId) { + ContactClassifierResponse response = this.contactClassifierResponseRepository.findByImageId(eImage.getId()); + if (response != null) { + if (response.getStatus() != ServiceCallStatus.NotSent) { + return response; + } + } else { + response = new ContactClassifierResponse(); + if (ebtsImageId != null) { + response.setEbtsImageId(ebtsImageId); + } + } + + response.setStatus(ServiceCallStatus.Pending); + response = this.contactClassifierResponseRepository.save(response); + + return response; + + } + + public Long processTshepii(EbtsImage eImage, Long ebtsImageId) { + TshepiiResponse response = this.tshepiiResponseRepository.findByImageId(eImage.getId()); + if (response == null) { + response = new TshepiiResponse(); + if (ebtsImageId != null) { + response.setEbtsImageId(ebtsImageId); + } + } + + response.setStatus(ServiceCallStatus.Pending); + response = this.tshepiiResponseRepository.save(response); + + try { + this.pushMessageToQueue(this.iwpWebProperties.getTshepiiQueueRequest(), + ByteString.copyFrom(EbtsImage.getImageBytes(eImage)), response.getImageId()); + } catch (IOException ex) { + log.info("Exception"); + response.setError(ex.getMessage()); + this.tshepiiResponseRepository.save(response); + } + + return response.getImageId(); + } + + public boolean requestTshepiiComparison(Long probeImageId, Long candidateImageId) { + TshepiiResponse probeResponse = this.tshepiiResponseRepository.findByImageId(probeImageId); + TshepiiResponse candResponse = this.tshepiiResponseRepository.findByImageId(candidateImageId); + + boolean cancelCall = false; + if (probeResponse == null || candResponse == null) { + return false; + } + + if (probeResponse.getStatus() != ServiceCallStatus.Received) { + log.info("Probe Status is: {}", probeResponse.getStatus()); + cancelCall = true; + } + + if (candResponse.getStatus() != ServiceCallStatus.Received) { + log.info("Candidate Status is: {}", probeResponse.getStatus()); + cancelCall = true; + } + + if (cancelCall) { + log.info("Cancelling Tshepii Comparison Request"); + return false; + } + + try { + + TshepiiCompareResponse compareResponse = new TshepiiCompareResponse(); + compareResponse.setImageId(probeResponse.getImageId()); + compareResponse.setEbtsImageId(probeResponse.getEbtsImageId()); + compareResponse.setCandidateImageId(candResponse.getImageId()); + compareResponse.setCandidateEbtsImageId(candResponse.getEbtsImageId()); + compareResponse.setStatus(ServiceCallStatus.Pending); + this.tshepiiCompareResponseRepository.save(compareResponse); + + + Tshepii.TShepiiCompareRequest compareRequest = Tshepii.TShepiiCompareRequest.newBuilder() + .setResponse1(Tshepii.TShepiiResponse.parseFrom(probeResponse.getServiceResponse())) + .setResponse2(Tshepii.TShepiiResponse.parseFrom(candResponse.getServiceResponse())).build(); + + this.jmsTemplate.convertAndSend(this.iwpWebProperties.getTshepiiQueueRequest(), + compareRequest.toByteArray()); + return true; + } catch (InvalidProtocolBufferException exc) { + log.info(exc.getMessage()); + } + + return false; + } + + public Long processIrisAnnotation(EbtsImage eImage, Long ebtsImageId) { + IrisAnnotationResponse response = this.irisAnnotationResponseRepository.findByImageId(eImage.getId()); + if (response != null) { + if (response.getStatus() != ServiceCallStatus.NotSent) { + return response.getImageId(); + } + } else { + response = new IrisAnnotationResponse(); + if (ebtsImageId != null) { + response.setEbtsImageId(ebtsImageId); + } + } + + response.setStatus(ServiceCallStatus.Pending); + response = this.irisAnnotationResponseRepository.save(response); + + try { + this.pushMessageToQueue(this.iwpWebProperties.getIrisAnnotationQueueRequest(), + ByteString.copyFrom(EbtsImage.getImageBytes(eImage)), response.getImageId()); + } catch (IOException ex) { + log.info("Exception"); + response.setError(ex.getMessage()); + this.irisAnnotationResponseRepository.save(response); + } + + return response.getImageId(); + } + + public Long processPdm(EbtsImage eImage, Long ebtsImageId) { + PdmResponse response = this.pdmResponseRepository.findByImageId(eImage.getId()); + if (response != null) { + if (response.getStatus() != ServiceCallStatus.NotSent) { + return response.getImageId(); + } + } else { + response = new PdmResponse(); + if (ebtsImageId != null) { + response.setEbtsImageId(ebtsImageId); + } + } + + response.setStatus(ServiceCallStatus.Pending); + response = this.pdmResponseRepository.save(response); + try { + this.pushMessageToQueue(this.iwpWebProperties.getPdmQueueRequest(), + ByteString.copyFrom(EbtsImage.getImageBytes(eImage)), response.getImageId()); + } catch (IOException ex) { + + response.setError(ex.getMessage()); + this.pdmResponseRepository.save(response); + } + return response.getImageId(); + } + + // Dual PDM; doesn't need a Long ebtsImageId since it can't be resubmitted. + public void processDualPDM(EbtsImage eImage) { + DualPDMResponse response = this.dualPDMResponseRepository.findByImageId(eImage.getId()); + + if (response == null) { + response = new DualPDMResponse(); + response.setEbtsImageId(eImage.getId()); + } + + response.setStatus(ServiceCallStatus.Pending); + + try { + response.setImageBytes(EbtsImage.getImageBytes(eImage)); + this.dualPDMResponseRepository.save(response); + response.setStatus(ServiceCallStatus.Received); + } catch (Exception ex) { + log.error("Failed to save image for id{}", eImage.getId()); + response.setError(ex.getMessage()); + this.dualPDMResponseRepository.save(response); + } + } + + public boolean requestDualPDMComparison(Long probeImageId, Long candidateImageId) { + // Retrieve the probe and candidate responses from the database + DualPDMResponse probeResponse = this.dualPDMResponseRepository.findByImageId(probeImageId); + DualPDMResponse candidateResponse = this.dualPDMResponseRepository.findByImageId(candidateImageId); + + // Validate that both responses exist + if (probeResponse == null || candidateResponse == null) { + log.error("Probe or Candidate response not found for IDs: Probe={}, Candidate={}", probeImageId, candidateImageId); + return false; + } + + // Validate that both responses have the "Received" status + boolean cancelCall = false; + if (probeResponse.getStatus() != ServiceCallStatus.Received) { + log.info("Probe Status is: {}", probeResponse.getStatus()); + cancelCall = true; + } + if (candidateResponse.getStatus() != ServiceCallStatus.Received) { + log.info("Candidate Status is: {}", candidateResponse.getStatus()); + cancelCall = true; + } + + if (cancelCall) { + log.info("Cancelling Dual PDM Comparison Request due to invalid statuses"); + return false; + } + + try { + // Create a new DualPDMCompareResponse entity with "Pending" status + DualPDMCompareResponse compareResponse = new DualPDMCompareResponse(); + compareResponse.setProbeId(probeImageId); + compareResponse.setCandidateId(candidateImageId); + compareResponse.setStatus(ServiceCallStatus.Pending); + DualPDMCompareResponse savedCompareResponse = this.dualPDMCompareResponseRepository.save(compareResponse); + + // Construct the DualPDMComparison Protocol Buffers message + Pdm.DualPDMComparison comparisonRequest = Pdm.DualPDMComparison.newBuilder() + .setProbe(ByteString.copyFrom(probeResponse.getImageBytes())) + .setCandidate(ByteString.copyFrom(candidateResponse.getImageBytes())) + .build(); + + log.info("Pushing Message to DualPDM Request Queue"); + + this.jmsTemplate.convertAndSend( + this.iwpWebProperties.getDualPDMQueueRequest(), + comparisonRequest.toByteArray(), + message -> { + message.setLongProperty("probeId", savedCompareResponse.getProbeId()); + message.setLongProperty("candidateId", savedCompareResponse.getCandidateId()); + return message; + } + ); + + return true; + } catch (Exception exc) { + log.error("Unexpected error while sending Dual PDM Comparison Request: {}", exc.getMessage()); + } + + return false; + } + + private void pushMessageToQueue(String queueName, ByteString bytes, Long id) { + log.info("Pushing Message to Queue: {}", queueName); + Iwp.ImageServiceQuery isq = Iwp.ImageServiceQuery.newBuilder().setImageId(id.toString()).setImageBytes(bytes) + .build(); + + this.jmsTemplate.convertAndSend(queueName, isq.toByteArray()); + } + +} diff --git a/web/src/main/java/org/mitre/iwp/web/jms/TshepiiReceiver.java b/web/src/main/java/org/mitre/iwp/web/jms/TshepiiReceiver.java new file mode 100644 index 0000000..c6548f7 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/jms/TshepiiReceiver.java @@ -0,0 +1,108 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.jms; + +import com.google.protobuf.*; +import org.mitre.iwp.buffers.Tshepii; +import org.mitre.iwp.web.model.*; +import org.mitre.iwp.web.service.TshepiiCompareResponseRepository; +import org.mitre.iwp.web.service.TshepiiResponseRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.stereotype.Component; + +import jakarta.jms.BytesMessage; +import jakarta.jms.JMSException; + +@Component +@ConditionalOnProperty(prefix = "iris", name = "tshepiiService", havingValue = "true") +public class TshepiiReceiver { + private static final Logger log = LoggerFactory.getLogger(TshepiiReceiver.class); + private final TshepiiResponseRepository tshepiiResponseRepository; + private final TshepiiCompareResponseRepository tshepiiCompareResponseRepository; + + @Autowired + public TshepiiReceiver( + TshepiiResponseRepository tshepiiResponseRepository, + TshepiiCompareResponseRepository tshepiiCompareResponseRepository) { + this.tshepiiResponseRepository = tshepiiResponseRepository; + this.tshepiiCompareResponseRepository = tshepiiCompareResponseRepository; + } + + @JmsListener(destination = "#{iwpWebProperties.getTshepiiQueueResponse()}") + public void processMessage(Object object) { + try { + BytesMessage bMessage = (BytesMessage) object; + + byte[] bytes = new byte[(int) bMessage.getBodyLength()]; + bMessage.readBytes(bytes); + Tshepii.TShepiiCompareResponse compareResponse = this.tryParseComparison(bytes); + + if(compareResponse == null){ + Tshepii.TShepiiResponse serviceResponse = Tshepii.TShepiiResponse.parseFrom(bytes); + Long responseImageId = Long.parseLong(serviceResponse.getImageId()); + + log.info("Tshepii Response for Image Id: {}", responseImageId); + TshepiiResponse tshepiiResponse = this.tshepiiResponseRepository.findByImageId(responseImageId); + + if (tshepiiResponse == null) { + tshepiiResponse = new TshepiiResponse(); + } + + tshepiiResponse.setServiceResponse(bytes); + tshepiiResponse.setStatus(ServiceCallStatus.Received); + + this.tshepiiResponseRepository.save(tshepiiResponse); + } else { + Long probeImageId = Long.parseLong(compareResponse.getCryptResponse().getImageId()); + Long candImageId = Long.parseLong(compareResponse.getCryptResponse2().getImageId()); + + log.info("Tshepii Compare Response for Probe Id: {} and Candidate Id: {}", + probeImageId, candImageId); + TshepiiCompareResponse tShepiiCompareResponse = this.tshepiiCompareResponseRepository.findByImageIdAndCandidateImageId(probeImageId, candImageId); + + if(tShepiiCompareResponse == null){ + tShepiiCompareResponse = new TshepiiCompareResponse(); + } + + tShepiiCompareResponse.setServiceResponse(bytes); + tShepiiCompareResponse.setStatus(ServiceCallStatus.Received); + + this.tshepiiCompareResponseRepository.save(tShepiiCompareResponse); + } + } catch (JMSException | InvalidProtocolBufferException jmsException) { + log.info(jmsException.getMessage()); + } + } + + public Tshepii.TShepiiCompareResponse tryParseComparison(byte[] bytes){ + try { + Tshepii.TShepiiCompareResponse response = Tshepii.TShepiiCompareResponse.parseFrom(bytes); + if(response.hasCryptResponse() && response.hasCryptResponse2()) { + return response; + } else { + if(response.hasCryptResponse()){ + log.info("Missing CryptResponse2"); + } else { + log.info("Missing CryptResponse"); + } + } + } catch(InvalidProtocolBufferException exc){ + log.info("Failed to parse Compare Response"); + } + return null; + } + + +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/AciiResponse.java b/web/src/main/java/org/mitre/iwp/web/model/AciiResponse.java new file mode 100644 index 0000000..48af516 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/AciiResponse.java @@ -0,0 +1,31 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import jakarta.persistence.Entity; + +@Entity +public class AciiResponse extends ServiceResponse { + public void setVertical(String vertical) { + this.vertical = vertical; + } + public String getVertical() { + return this.vertical; + } + + public void setHorizontal(String horizontal) { + this.horizontal = horizontal; + } + public String getHorizontal() { return this.horizontal; } + + String vertical; + String horizontal; +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/AdaptiveContrastResponse.java b/web/src/main/java/org/mitre/iwp/web/model/AdaptiveContrastResponse.java new file mode 100644 index 0000000..9558a76 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/AdaptiveContrastResponse.java @@ -0,0 +1,22 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import jakarta.persistence.Entity; + +@Entity +public class AdaptiveContrastResponse extends ServiceResponse { + + public byte[] getImageBytes() { return this.imageBytes; } + public void setImageBytes(byte[] imageBytes) { this.imageBytes = imageBytes; } + + byte[] imageBytes; +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/Annotation.java b/web/src/main/java/org/mitre/iwp/web/model/Annotation.java new file mode 100644 index 0000000..e2dd11d --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/Annotation.java @@ -0,0 +1,153 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +@Entity +public class Annotation { + private static final Logger log = LoggerFactory.getLogger(Annotation.class); + + public Annotation(){} + + public Annotation(String id, String text, String type, Long creationDateEpoch, AnnotationValue value, List tempChildren, String parentId) { + this.id = id; + this.text = text; + this.type = type; + this.creationDateEpoch = creationDateEpoch; + this.value = value; + this.tempChildren = tempChildren; + this.parentId = parentId; + } + + @Id + @Column(length = 30) + private String id; + + public String getId() { + return this.id; + } + + public void setId(String id) { + this.id = id; + } + + private String text; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + private Long creationDateEpoch = 0L; + + public void setCreationDateEpoch(Long creationDateEpoch) { + this.creationDateEpoch = creationDateEpoch; + } + + public Long getCreationDateEpoch() { + return this.creationDateEpoch; + } + // NOTE: Because of the current way we're doing saving of annotations/groups, + // we can't easily support cascading of delete. This needs to be handled + // separately! + @OneToOne(mappedBy = "annotation", cascade = { CascadeType.MERGE, CascadeType.PERSIST }, orphanRemoval = true) + private AnnotationValue value; + + public AnnotationValue getValue() { + return this.value; + } + + public void setValue(AnnotationValue value) { + if (this.value != null) { + this.value.setAnnotation(null); + } + if (value != null) { + value.setAnnotation(this); + } + + this.value = value; + } + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + private List tempChildren = new ArrayList<>(); + + public List getTempChildren() { + Comparator creationDateEpochSorter + = (a1, a2) -> a1.getCreationDateEpoch().compareTo(a2.getCreationDateEpoch()); + this.tempChildren.sort(creationDateEpochSorter.reversed()); + + return tempChildren; + } + + public void setTempChildren(List children) { + this.tempChildren = children; + } + + private String parentId; + + public String getParentId() { + return parentId; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + String newLineString = System.getProperty("line.separator"); + + result.append(this.getClass().getName()); + result.append(" Object {"); + result.append(newLineString); + + // determine fields declared in this class only (no fields of superclass) + java.lang.reflect.Field[] fields = this.getClass().getDeclaredFields(); + + // print field names paired with their values + for (java.lang.reflect.Field field : fields) { + result.append(" "); + result.append(field.getName()); + result.append(": "); + // requires access to private field: + try { + result.append(field.get(this)); + } catch (IllegalAccessException e) { + log.error(e.getMessage(), e); + } + result.append(newLineString); + } + result.append("}"); + + return result.toString(); + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/AnnotationValue.java b/web/src/main/java/org/mitre/iwp/web/model/AnnotationValue.java new file mode 100644 index 0000000..9ce25c9 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/AnnotationValue.java @@ -0,0 +1,88 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import jakarta.persistence.*; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Entity +@JsonIgnoreProperties(ignoreUnknown = true) +public class AnnotationValue { + public AnnotationValue(){ + + } + + public AnnotationValue(Long id, List points, Annotation annotation, Map data) { + this.id = id; + this.points = points; + this.annotation = annotation; + this.data = data; + } + + @Id + @GeneratedValue + private Long id; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + @ElementCollection + @CollectionTable(name = "point", joinColumns = @JoinColumn(name = "annotation_value_id")) + private List points = new ArrayList<>(); + + public List getPoints() { + return points; + } + + public void setPoints(List points) { + this.points = points; + } + + @OneToOne + @JoinColumn(name = "annotation_id") + @JsonIgnore + private Annotation annotation = new Annotation(); + + public Annotation getAnnotation() { + return this.annotation; + } + + public void setAnnotation(Annotation annotation) { + this.annotation = annotation; + } + + @ElementCollection + @MapKeyColumn(name = "name") + @Column(name = "value") + @CollectionTable(name = "annotation_value_data_map", joinColumns = { + @JoinColumn(name = "annotation_value_data_id", referencedColumnName = "id") }) + private Map data = new HashMap<>(); + + public Map getData() { + return this.data; + } + + public void setData(Map data) { + this.data = data; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/AppNotification.java b/web/src/main/java/org/mitre/iwp/web/model/AppNotification.java new file mode 100644 index 0000000..216a450 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/AppNotification.java @@ -0,0 +1,62 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +@Entity +public class AppNotification { + public AppNotification(){ + + } + + public AppNotification(Long id, Long userId, String message, String icon, String action, String dateCreated, boolean isNew) { + this.id = id; + this.userId = userId; + this.message = message; + this.icon = icon; + this.action = action; + this.dateCreated = dateCreated; + this.isNew = isNew; + } + + @Id + @GeneratedValue + private long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private Long userId; + public Long getUserId(){return this.userId;} + public void setUserId(Long userId) { this.userId = userId; } + + private String message; + public String getMessage() { return this.message; } + public void setMessage(String message) { this.message = message; } + + private String icon; + public String getIcon() { return this.icon; } + public void setIcon(String icon) { this.icon = icon; } + + private String action; + public String getAction() { return this.action; } + public void setAction(String action) { this.action = action; } + + private String dateCreated; + public String getDateCreated() { return this.dateCreated; } + public void setDateCreated(String dateCreated) { this.dateCreated = dateCreated; } + + private boolean isNew; + public boolean getIsNew() { return this.isNew; } + public void setIsNew(boolean isNew) { this.isNew = isNew; } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/BiqtResponse.java b/web/src/main/java/org/mitre/iwp/web/model/BiqtResponse.java new file mode 100644 index 0000000..18212ff --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/BiqtResponse.java @@ -0,0 +1,51 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import jakarta.persistence.Entity; + +@Entity +public class BiqtResponse extends ServiceResponse { + int quality = -1; + public void setQuality(int quality) { + if(quality >= 0 && quality <= 100) { + this.quality = quality; + } + } + public int getQuality() { + return quality; + } + + int irisCenterX; + public void setIrisCenterX(int irisCenterX) { + this.irisCenterX = irisCenterX; + } + public int getIrisCenterX() { + return irisCenterX; + } + + int irisCenterY; + public int getIrisCenterY() { + return irisCenterY; + } + public void setIrisCenterY(int irisCenterY) { + this.irisCenterY = irisCenterY; + } + + int percentVisibleIris; + public int getPercentVisibleIris() { return this.percentVisibleIris; } + public void setPercentVisibleIris(int percentVisibleIris) { this.percentVisibleIris = percentVisibleIris; } + + int irisDiameter; + public int getIrisDiameter() { return this.irisDiameter; } + public void setIrisDiameter(int irisDiameter) { this.irisDiameter = irisDiameter; } + +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/Candidate.java b/web/src/main/java/org/mitre/iwp/web/model/Candidate.java new file mode 100644 index 0000000..93d46f8 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/Candidate.java @@ -0,0 +1,116 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class Candidate { + private static final Logger log = LoggerFactory.getLogger(Candidate.class); + + public Candidate(){ + + } + + public Candidate(Long id, String score, String subjectIdentifier, List imageList, String adjudicationResults, String note) { + this.id = id; + this.score = score; + this.subjectIdentifier = subjectIdentifier; + this.imageList = imageList; + this.adjudicationResults = adjudicationResults; + this.note = note; + } + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String score; + public String getScore() { + return score; + } + public void setScore(String score) { + this.score = score; + } + + private String subjectIdentifier; + public String getSubjectIdentifier() { + return subjectIdentifier; + } + public void setSubjectIdentifier(String subjectIdentifier) { + this.subjectIdentifier = subjectIdentifier; + } + + @OneToMany(cascade = { CascadeType.MERGE, CascadeType.REFRESH, CascadeType.REMOVE }, + orphanRemoval = true) + private List imageList = new ArrayList<>(); + public List getImageList() { return this.imageList; } + public void setImageList(List imageList) { + this.imageList = imageList; + } + + public void addImage(ImageData imageData) { + this.imageList.add(imageData); + } + + public void removeImage(ImageData imageData) { + if (this.imageList.contains(imageData)) { + this.imageList.remove(imageData); + } + } + + private String adjudicationResults; + public String getAdjudicationResults() { return adjudicationResults; } + public void setAdjudicationResults(String adjudicationResults) { this.adjudicationResults = adjudicationResults; } + + private String note; + public String getNote() { return this.note; } + public void setNote( String note ) { this.note = note; } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + String newLineString = System.getProperty("line.separator"); + + result.append(this.getClass().getName()); + result.append(" Object {"); + result.append(newLineString); + + //determine fields declared in this class only (no fields of superclass) + java.lang.reflect.Field[] fields = this.getClass().getDeclaredFields(); + + //print field names paired with their values + for (java.lang.reflect.Field field : fields) { + result.append(" "); + try { + result.append(field.getName()); + result.append(": "); + //requires access to private field: + result.append(field.get(this)); + } + catch (IllegalAccessException e) { + log.error(e.getMessage(), e); + } + result.append(newLineString); + } + result.append("}"); + + return result.toString(); + } + +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/CaseInformation.java b/web/src/main/java/org/mitre/iwp/web/model/CaseInformation.java new file mode 100644 index 0000000..e7dab29 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/CaseInformation.java @@ -0,0 +1,96 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.*; + +@Entity +public class CaseInformation { + private static final Logger log = LoggerFactory.getLogger(CaseInformation.class); + + public CaseInformation(){ + + } + + public CaseInformation(Long id, Long comparisonDate, Long receivedDate, Person requestedBy) { + this.id = id; + this.comparisonDate = comparisonDate; + this.receivedDate = receivedDate; + this.requestedBy = requestedBy; + } + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER) + public Person requestedBy; + + private Long comparisonDate = 0L; + private Long receivedDate = 0L; + + public Person getRequestedBy() { + return requestedBy; + } + public void setRequestedBy(Person requestedBy) { + this.requestedBy = requestedBy; + } + + public Long getComparisonDate() { + return comparisonDate; + } + public void setComparisonDate(Long comparisonDate) { + this.comparisonDate = comparisonDate; + } + + public Long getReceivedDate() { + return receivedDate; + } + public void setReceivedDate(Long receivedDate) { + this.receivedDate = receivedDate; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + String newLineString = System.getProperty("line.separator"); + + result.append(this.getClass().getName()); + result.append(" Object {"); + result.append(newLineString); + + //determine fields declared in this class only (no fields of superclass) + java.lang.reflect.Field[] fields = this.getClass().getDeclaredFields(); + + //print field names paired with their values + for (java.lang.reflect.Field field : fields) { + result.append(" "); + try { + result.append(field.getName()); + result.append(": "); + //requires access to private field: + result.append(field.get(this)); + } + catch (IllegalAccessException ex) { + log.error(ex.getMessage(), ex); + } + result.append(newLineString); + } + result.append("}"); + + return result.toString(); + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/ContactClassifierResponse.java b/web/src/main/java/org/mitre/iwp/web/model/ContactClassifierResponse.java new file mode 100644 index 0000000..e8cadbe --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/ContactClassifierResponse.java @@ -0,0 +1,32 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import jakarta.persistence.Entity; + +@Entity +public class ContactClassifierResponse extends ServiceResponse { + String detectionCode; + public String getDetectionCode() { + return detectionCode; + } + public void setDetectionCode(String detectionCode) { + this.detectionCode = detectionCode; + } + + String processTimeMS; + public String getProcessTimeMS() { + return processTimeMS; + } + public void setProcessTimeMS(String processTimeMS) { + this.processTimeMS = processTimeMS; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/ContactType.java b/web/src/main/java/org/mitre/iwp/web/model/ContactType.java new file mode 100644 index 0000000..3b278d5 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/ContactType.java @@ -0,0 +1,49 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.HashMap; +import java.util.Map; + +public enum ContactType { + NONE(0), + COSMETIC(1), + CLEAR(2), + EMPTY(3); + + private int value; + private static Map map = new HashMap<>(); + + ContactType(int value) { + this.value = value; + } + + @JsonValue + public int toValue() { + return ordinal(); + } + + static { + for (ContactType label : ContactType.values()) { + map.put(label.value, label); + } + } + + public static ContactType valueOf(int label) { + return map.get(label); + } + + public int getValue() { + return value; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/CreateIrisSearchRequest.java b/web/src/main/java/org/mitre/iwp/web/model/CreateIrisSearchRequest.java new file mode 100644 index 0000000..c8a7dbe --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/CreateIrisSearchRequest.java @@ -0,0 +1,77 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import jakarta.persistence.*; + +@Entity +public class CreateIrisSearchRequest { + public CreateIrisSearchRequest(){ + + } + + public CreateIrisSearchRequest(Long id, EbtsMessageRequest message, UserAuthentication user, Long creationDateEpoch, Boolean isFinished, String filePath) { + this.id = id; + this.message = message; + this.user = user; + this.creationDateEpoch = creationDateEpoch; + this.isFinished = isFinished; + this.filePath = filePath; + } + + @Id + @GeneratedValue + private Long id; + public Long getId() { + return this.id; + } + public void setId(Long id) { + this.id = id; + } + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private EbtsMessageRequest message; + public void setMessage(EbtsMessageRequest message) { + this.message = message; + } + public EbtsMessageRequest getMessage() { + return this.message; + } + + @ManyToOne + private UserAuthentication user; + public UserAuthentication getUser() { + return this.user; + } + public void setUser(UserAuthentication user) { + this.user = user; + } + + private Long creationDateEpoch = 0L; + public void setCreationDateEpoch(Long creationDateEpoch) { + this.creationDateEpoch = creationDateEpoch; + } + public Long getCreationDateEpoch() { + return this.creationDateEpoch; + } + + private Boolean isFinished = false; + public void setIsFinished(Boolean isFinished) { + this.isFinished = isFinished; + } + public Boolean getIsFinished() { + return this.isFinished; + } + + private String filePath; + public String getFilePath() { return this.filePath; } + public void setFilePath(String filePath) { this.filePath = filePath; } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/Crypt.java b/web/src/main/java/org/mitre/iwp/web/model/Crypt.java new file mode 100644 index 0000000..e33d0e9 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/Crypt.java @@ -0,0 +1,38 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import com.google.gson.annotations.Expose; + +import java.util.ArrayList; +import java.util.List; + +public class Crypt { + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + public Crypt(List pointList){ + this.pointList = pointList; + } + public Crypt(){ + this.pointList = new ArrayList<>(); + } + + @Expose + List pointList; + public void setPointList(List pList) { + this.pointList = pList; + } + public List getPointList() { + return this.pointList; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/DualPDMCompareResponse.java b/web/src/main/java/org/mitre/iwp/web/model/DualPDMCompareResponse.java new file mode 100644 index 0000000..a1009e2 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/DualPDMCompareResponse.java @@ -0,0 +1,76 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.mitre.iwp.buffers.Pdm; +import com.google.protobuf.InvalidProtocolBufferException; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Lob; + +@Entity +public class DualPDMCompareResponse extends ServiceResponse { + private static final Logger log = LoggerFactory.getLogger(DualPDMCompareResponse.class); + + private Long probeId; + private Long candidateId; + private Long candidateEbtsImageId; + + private byte[] probe; + private byte[] candidate; + + @Lob + @Column(columnDefinition="LONGBLOB") + private byte[] serviceResponse; + public void setServiceResponse(byte[] response) { this.serviceResponse = response; } + public byte[] getServiceResponse() { return this.serviceResponse; } + + // Probe - Getter / Setters + public byte[] getProbe() { return probe; } + public void setProbe(byte[] probe) { this.probe = probe; } + + // Candidate - Getter / Setters + public byte[] getCandidate() { return candidate; } + public void setCandidate(byte[] candidate) { this.candidate = candidate; } + + // Ids - Getter / Setters + public Long getProbeId() { return probeId; } + public void setProbeId(Long probeId) { this.probeId = probeId; } + + public Long getCandidateId() { return candidateId; } + public void setCandidateId(Long candidateId) { this.candidateId = candidateId; } + + public Long getCandidateEbtsImageId() { return candidateEbtsImageId; } + public void setCandidateEbtsImageId(Long candidateEbtsImageId) { this.candidateEbtsImageId = candidateEbtsImageId; } + + + public boolean buildFromServiceResponse() { + + if(serviceResponse.length == 0){ + log.info("Dual PDM Compare Response ServiceResponse length is 0"); + return false; + } + + try { + Pdm.DualPDMComparison comparison = Pdm.DualPDMComparison.parseFrom(serviceResponse); + DualPDMCompareResponse response = new DualPDMCompareResponse(); + response.setProbe(comparison.getProbe().toByteArray()); + response.setCandidate(comparison.getCandidate().toByteArray()); + return true; + + } catch (InvalidProtocolBufferException e) { + log.info(e.getMessage()); + return false; + } + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/DualPDMResponse.java b/web/src/main/java/org/mitre/iwp/web/model/DualPDMResponse.java new file mode 100644 index 0000000..319f0bf --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/DualPDMResponse.java @@ -0,0 +1,42 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Lob; +import com.google.gson.annotations.Expose; + + +@Entity +public class DualPDMResponse extends ServiceResponse { + + @Lob + @Column(columnDefinition="LONGBLOB") + @Expose + private byte[] imageBytes; + + public DualPDMResponse(){ + // Empty Constructor to satisfy JPA requirement + } + + public int getByteSize() { + return imageBytes.length; + } + + // Getter and Setters + public byte[] getImageBytes() { + return imageBytes; + } + public void setImageBytes(byte[] imageBytes) { + this.imageBytes = imageBytes; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/EbtsMessageRequest.java b/web/src/main/java/org/mitre/iwp/web/model/EbtsMessageRequest.java new file mode 100644 index 0000000..f531d67 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/EbtsMessageRequest.java @@ -0,0 +1,174 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import org.apache.commons.lang3.StringUtils; +import jakarta.persistence.*; +import java.util.*; + +@Entity +public class EbtsMessageRequest { + public EbtsMessageRequest(){ + + } + + public EbtsMessageRequest(Long id, Map mapData, ImageData imageData, String cinPrefix, String cinIdentifier, int numberOfCandidates, int caseExtension, String dateOfSubmission, String destinationAgencyIdentifier, String originatingAgencyIdentifier, String transactionControlNumber, String attentionIndicator, String sourceAgency, String irisCaptureDate, int rotationOfEye, int rotationUncertainty, List errorMessages, List invalidFields) { + this.id = id; + this.mapData = mapData; + this.imageData = imageData; + this.cinPrefix = cinPrefix; + this.cinIdentifier = cinIdentifier; + this.numberOfCandidates = numberOfCandidates; + this.caseExtension = caseExtension; + this.dateOfSubmission = dateOfSubmission; + this.destinationAgencyIdentifier = destinationAgencyIdentifier; + this.originatingAgencyIdentifier = originatingAgencyIdentifier; + this.transactionControlNumber = transactionControlNumber; + this.attentionIndicator = attentionIndicator; + this.sourceAgency = sourceAgency; + this.irisCaptureDate = irisCaptureDate; + this.rotationOfEye = rotationOfEye; + this.rotationUncertainty = rotationUncertainty; + this.errorMessages = errorMessages; + this.invalidFields = invalidFields; + } + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + public boolean isValid() { + invalidFields.clear(); + checkField(dateOfSubmission, "DAT"); + checkField(destinationAgencyIdentifier, "DAI"); + checkField(originatingAgencyIdentifier, "ORI"); + checkField(transactionControlNumber, "TCN"); + checkField(attentionIndicator, "ATN"); + checkField(cinPrefix, "CIN_PRE"); + checkField(cinIdentifier, "CIN_ID"); + if(numberOfCandidates < 20 || numberOfCandidates > 50){ + invalidFields.add("NCR"); + } + checkField(sourceAgency, "SRC"); + checkField(irisCaptureDate, "ICD"); + + if(this.imageData != null){ + checkField(imageData.getImageData(), "DATA"); + + if(null == this.imageData.getEyeLabel()) + invalidFields.add("ELR"); + } + return invalidFields.isEmpty(); + } + + public String getErrors() { + if(invalidFields.isEmpty()) + return ""; + + return String.format("Missing mandatory fields '%s'", String.join("', '", invalidFields)); + } + + public void checkField(String value, String mnemonic){ + if(StringUtils.isEmpty(value)){ + invalidFields.add(mnemonic); + } + } + + public void checkField(byte[] value, String mnemonic){ + if(value.length == 0){ + invalidFields.add(mnemonic); + } + } + + @ElementCollection + private Map mapData = new HashMap<>(); + public Map getMapData() { return mapData; } + public void setMapData(HashMap mapData) { this.mapData = mapData; } + + @OneToOne(cascade = CascadeType.ALL) + private ImageData imageData; + public ImageData getImageData() { return this.imageData; } + public void setImageData(ImageData imageData) { + this.imageData = imageData; + } + + private String cinPrefix = ""; // 2.010a - cin_pre + public String getCinPrefix() { return cinPrefix; } + public void setCinPrefix(String cinPrefix) { this.cinPrefix = cinPrefix; } + + private String cinIdentifier = ""; // 2.010b - cin_id + public String getCinIdentifier() { return cinIdentifier; } + public void setCinIdentifier(String cinIdentifier) { this.cinIdentifier = cinIdentifier; } + + private int numberOfCandidates; // 2.079 - ncr + public int getNumberOfCandidates() { return numberOfCandidates; } + public void setNumberOfCandidates(int numberOfCandidates) { this.numberOfCandidates = numberOfCandidates; } + + private int caseExtension; //2.011 - cix + public int getCaseExtension() { return caseExtension; } + public void setCaseExtension(int caseExtension) { this.caseExtension = caseExtension; } + + private String dateOfSubmission = ""; // 1.005 - dat + public String getDateOfSubmission() { return dateOfSubmission; } + public void setDateOfSubmission(String dateOfSubmission) { this.dateOfSubmission = dateOfSubmission; } + + private String destinationAgencyIdentifier = ""; // 1.007 - dai + public String getDestinationAgencyIdentifier() { return destinationAgencyIdentifier; } + public void setDestinationAgencyIdentifier(String destinationAgencyIdentifier) { this.destinationAgencyIdentifier = destinationAgencyIdentifier; } + + private String originatingAgencyIdentifier; // 1.008 - ori + public String getOriginatingAgencyIdentifier() { return originatingAgencyIdentifier; } + public void setOriginatingAgencyIdentifier(String originatingAgencyIdentifier) { this.originatingAgencyIdentifier = originatingAgencyIdentifier; } + + private String transactionControlNumber; // 1.009 - tcn + public String getTransactionControlNumber() { return transactionControlNumber; } + public void setTransactionControlNumber(String transactionControlNumber) { this.transactionControlNumber = transactionControlNumber; } + + private String attentionIndicator; // 2.006 - atn + public String getAttentionIndicator() { return attentionIndicator; } + public void setAttentionIndicator(String attentionIndicator) { this.attentionIndicator = attentionIndicator; } + + private String sourceAgency; // 17.004 - src + public String getSourceAgency() { return sourceAgency; } + public void setSourceAgency(String sourceAgency) { this.sourceAgency = sourceAgency; } + + private String irisCaptureDate; // 17.005 - icd + public String getIrisCaptureDate() { return irisCaptureDate; } + public void setIrisCaptureDate(String irisCaptureDate) { this.irisCaptureDate = irisCaptureDate; } + + private int rotationOfEye; //17.014 - rae + public int getRotationOfEye() { return rotationOfEye; } + public void setRotationOfEye(int rotationOfEye) { this.rotationOfEye = rotationOfEye; } + + private int rotationUncertainty; // 17.015 - rau + public int getRotationUncertainty() { return rotationUncertainty; } + public void setRotationUncertainty(int rotationUncertainty) { this.rotationUncertainty = rotationUncertainty; } + + @ElementCollection + private List errorMessages = new ArrayList<>(); + public List getErrorMessages() { return errorMessages; } + public void addErrorMessages(String errorMessage) { this.errorMessages.add(errorMessage); } + + @ElementCollection + private List invalidFields = new ArrayList<>(); + public String getInvalidFieldsString() { return String.join(", ", invalidFields); } + public List getInvalidFields() { return this.invalidFields; } + + private int tempEbtsImageId = -1; + public int getTempEbtsImageId() { + return this.tempEbtsImageId; + } + public void setTempEbtsImageId(int imageDataId){ + this.tempEbtsImageId = imageDataId; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/EyeLabel.java b/web/src/main/java/org/mitre/iwp/web/model/EyeLabel.java new file mode 100644 index 0000000..733fc94 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/EyeLabel.java @@ -0,0 +1,49 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.HashMap; +import java.util.Map; + +public enum EyeLabel { + UNDEFINED(0), + RIGHT(1), + LEFT(2), + EMPTY(3); + + private int value; + private static Map map = new HashMap<>(); + + EyeLabel(int value) { + this.value = value; + } + + @JsonValue + public int toValue() { + return ordinal(); + } + + static { + for (EyeLabel label : EyeLabel.values()) { + map.put(label.value, label); + } + } + + public static EyeLabel valueOf(int label) { + return map.get(label); + } + + public int getValue() { + return value; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/ImageData.java b/web/src/main/java/org/mitre/iwp/web/model/ImageData.java new file mode 100644 index 0000000..065b22c --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/ImageData.java @@ -0,0 +1,290 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.*; +import java.util.*; + +@Entity +public class ImageData { + private static final Logger log = LoggerFactory.getLogger(ImageData.class); + + public ImageData() { + } + + public ImageData(Long ebtsImageId) { + this.ebtsImageId = ebtsImageId; + } + + public ImageData(Long id, Long ebtsImageId, Long unrollImageId, Map unrollRingData, + Long rasterizeEbtsImageId, EyeLabel eyeLabel, ContactType contactType, + List annotations, String aciiHorizontal, boolean reported, + String imageNotes, String dme, String imageData, String unrollImageData, + byte[] imageBytes, byte[] unrollImageBytes, + int recordType, int recordIndex, int qualityScore) { + this.id = id; + this.ebtsImageId = ebtsImageId; + this.unrollImageId = unrollImageId; + this.unrollRingData = unrollRingData; + this.rasterizeEbtsImageId = rasterizeEbtsImageId; + this.eyeLabel = eyeLabel; + this.contactType = contactType; + this.annotations = annotations; + this.aciiHorizontal = aciiHorizontal; + this.reported = reported; + this.imageNotes = imageNotes; + this.dme = dme; + this.iData = imageData; + this.unrollImageData = unrollImageData; + this.imageBytes = imageBytes; + this.unrollImageBytes = unrollImageBytes; + this.recordType = recordType; + this.recordIndex = recordIndex; + this.qualityScore = qualityScore; + } + + @Id + @GeneratedValue + private Long id; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + private Long ebtsImageId; + + public Long getEbtsImageId() { + return this.ebtsImageId; + } + + public void setEbtsImageId(Long ebtsImageId) { + this.ebtsImageId = ebtsImageId; + } + + private Long unrollImageId; + + public Long getUnrollImageId() { + return this.unrollImageId; + } + + public void setUnrollImageId(Long unrollImageId) { + this.unrollImageId = unrollImageId; + } + + @ElementCollection + @MapKeyColumn(name = "name") + @Column(name = "value") + @CollectionTable(name = "unroll_ring_map", joinColumns = { @JoinColumn(name = "unroll_id") }) + private Map unrollRingData = new HashMap<>(); + + public Map getUnrollRingData() { + return this.unrollRingData; + } + + public void setUnrollRingData(Map unrollRingData) { + this.unrollRingData = unrollRingData; + } + + private Long rasterizeEbtsImageId; + + public Long getRasterizeEbtsImageId() { + return this.rasterizeEbtsImageId; + } + + public void setRasterizeEbtsImageId(Long rasterEbtsImageId) { + this.rasterizeEbtsImageId = rasterEbtsImageId; + } + + @Enumerated(value = EnumType.STRING) + private EyeLabel eyeLabel = EyeLabel.UNDEFINED; + + public EyeLabel getEyeLabel() { + return this.eyeLabel; + } + + public void setEyeLabel(EyeLabel label) { + this.eyeLabel = label; + } + + @Enumerated(value = EnumType.STRING) + private ContactType contactType = ContactType.NONE; + + public ContactType getContactType() { + return this.contactType; + } + + public void setContactType(ContactType type) { + this.contactType = type; + } + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + private List annotations = new ArrayList<>(); + + public List getAnnotations() { + return this.annotations; + } + + public void setAnnotations(List annotations) { + this.annotations = annotations; + } + + private String aciiHorizontal; + + public String getAciiHorizontal() { + return this.aciiHorizontal; + } + + public void setAciiHorizontal(String response) { + this.aciiHorizontal = response; + } + + private boolean reported = false; + + public boolean isReported() { + return this.reported; + } + + public void setReported(boolean reported) { + this.reported = reported; + } + + private String imageNotes = ""; + + public String getImageNotes() { + return this.imageNotes; + } + + public void setImageNotes(String imageNotes) { + this.imageNotes = imageNotes; + } + + private String dme = ""; + + public String getDme() { + return this.dme; + } + + public void setDme(String dme) { + this.dme = dme; + } + + @Transient + @Lob + private String iData; + + public String getImageData() { + return this.iData; + } + + public void setImageData(String imageData) { + this.iData = imageData; + } + + @Transient + private String unrollImageData; + + public String getUnrollImageData() { + return this.unrollImageData; + } + + public void setUnrollImageData(String unrollImageData) { + this.unrollImageData = unrollImageData; + } + + @Transient + private byte[] imageBytes; + + public byte[] getImageBytes() { + return this.imageBytes; + } + + public void setImageBytes(byte[] imageBytes) { + this.imageBytes = imageBytes; + } + + @Transient + private byte[] unrollImageBytes = new byte[0]; + + public byte[] getUnrollImageBytes() { + return this.unrollImageBytes; + } + + public void setUnrollImageBytes(byte[] unrollImageBytes) { + this.unrollImageBytes = unrollImageBytes; + } + + private int recordType; + + public int getRecordType() { + return this.recordType; + } + + public void setRecordType(int recordType) { + this.recordType = recordType; + } + + private int recordIndex; + + public int getRecordIndex() { + return this.recordIndex; + } + + public void setRecordIndex(int recordIndex) { + this.recordIndex = recordIndex; + } + + private int qualityScore; + + public int getQualityScore() { + return this.qualityScore; + } + + public void setQualityScore(int qualityScore) { + this.qualityScore = qualityScore; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + String newLineString = System.getProperty("line.separator"); + + result.append(this.getClass().getName()); + result.append(" Object {"); + result.append(newLineString); + + // determine fields declared in this class only (no fields of superclass) + java.lang.reflect.Field[] fields = this.getClass().getDeclaredFields(); + + // print field names paired with their values + for (java.lang.reflect.Field field : fields) { + result.append(" "); + try { + result.append(field.getName()); + result.append(": "); + // requires access to private field: + result.append(field.get(this)); + } catch (IllegalAccessException ex) { + log.error(ex.getMessage(), ex); + } + result.append(newLineString); + } + result.append("}"); + + return result.toString(); + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/ImageFile.java b/web/src/main/java/org/mitre/iwp/web/model/ImageFile.java new file mode 100644 index 0000000..b674e5d --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/ImageFile.java @@ -0,0 +1,33 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class ImageFile { + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + String fileHash; + public String getFileHash() { return this.fileHash; } + public void setFileHash(String fileHash) { this.fileHash = fileHash; } + + @ElementCollection(fetch=FetchType.EAGER) + private List imageHash = new ArrayList<>(); + public List getImageHash() { return this.imageHash; } + public void setImageHash(List imageHash) { this.imageHash = imageHash; } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/IrisAnnotationResponse.java b/web/src/main/java/org/mitre/iwp/web/model/IrisAnnotationResponse.java new file mode 100644 index 0000000..f0f2215 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/IrisAnnotationResponse.java @@ -0,0 +1,99 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import com.google.gson.annotations.Expose; +import com.google.protobuf.InvalidProtocolBufferException; +import org.mitre.iwp.buffers.IrisAnnotation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class IrisAnnotationResponse extends ServiceResponse { + private static final Logger log = LoggerFactory.getLogger(IrisAnnotationResponse.class); + + @Lob + @Column(columnDefinition="LONGBLOB") + private byte[] serviceResponse; + public void setServiceResponse(byte[] response) { this.serviceResponse = response; } + public byte[] getServiceResponse() { return this.serviceResponse; } + + + @Transient + @Expose + private String response; + + @Transient + @Expose + private String responseCode; + + @Transient + @Expose + private String responseMessage; + + @Transient + @Expose + private String responseTimeMs; + + @Transient + @Expose + private List polygonList = new ArrayList<>(); + public void setPolygonList(List polygonList) { + this.polygonList = polygonList; + } + public List getPolygonList() { + return this.polygonList; + } + + public boolean buildIrisAnnotationReply(){ + if(this.serviceResponse.length == 0){ + log.info("Service Response is empty"); + return false; + } + + try { + IrisAnnotation.IrisAnnotationReply irisAnnotationReply = IrisAnnotation.IrisAnnotationReply.parseFrom(this.serviceResponse); + + this.responseCode = irisAnnotationReply.getResponseCode(); + this.responseTimeMs = irisAnnotationReply.getResponseTimeMS(); + this.response = irisAnnotationReply.getResponse(); + this.responseCode = irisAnnotationReply.getResponseCode(); + + if(this.getEbtsImageId() == null || this.getEbtsImageId() == 0){ + this.setEbtsImageId(Long.parseLong(irisAnnotationReply.getId())); + } + + for(IrisAnnotation.Annotation annotation : irisAnnotationReply.getResultsList()){ + for(IrisAnnotation.Polygon annoPoly : annotation.getPolygonsList()) { + Polygon poly = new Polygon(); + poly.setCode(annotation.getId()); + + for(IrisAnnotation.Point point: annoPoly.getOutline().getPointsList()){ + poly.getOutline().add(new Point(point.getX(), point.getY())); + } + + this.polygonList.add(poly); + } + } + + } catch (InvalidProtocolBufferException e) { + this.setError(e.getMessage()); + log.info(e.getMessage()); + return false; + } + + return true; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/PdmDeformedImageResponse.java b/web/src/main/java/org/mitre/iwp/web/model/PdmDeformedImageResponse.java new file mode 100644 index 0000000..86b5cc9 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/PdmDeformedImageResponse.java @@ -0,0 +1,103 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import java.io.Serializable; + +import com.google.gson.annotations.Expose; + +import jakarta.persistence.*; + + +@Entity +public class PdmDeformedImageResponse implements Serializable { + + // This is the unique id for the deformed image. It is the imageId and alpha score combined together. + @Id + @Expose + private String deformedId; + @Lob + @Column(columnDefinition="LONGBLOB") + @Expose + private byte[] imageData; + @Expose + private float score; + @Expose + private String imageType; + @Expose + private int width; + @Expose + private int height; + @Expose + private Long ebtsImageId; + @Expose + private Long imageId; + + // Getters and setters + public String getDeformedId() { + return deformedId; + } + + public void setDeformedId(Long imageId, String alphaScore) { + this.deformedId = String.join(".", Long.toString(imageId), alphaScore); + } + + public byte[] getImageData() { + return imageData; + } + + public void setImageData(byte[] imageData) { + this.imageData = imageData; + } + + public float getScore() { + return score; + } + + public void setScore(float score) { + this.score = score; + } + + public String getImageType() { + return imageType; + } + + public void setImageType(String imageType) { + this.imageType = imageType; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public void setEbtsImageId(Long ebtsImageId) { this.ebtsImageId = ebtsImageId; } + public Long getEbtsImageId() { return this.ebtsImageId; } + + public void setImageId(Long imageId) { this.imageId = imageId; } + public Long getImageId() { return this.imageId; } + + public PdmDeformedImageResponse(){ + // Empty Constructor to satisfy JPA requirement + } + +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/PdmResponse.java b/web/src/main/java/org/mitre/iwp/web/model/PdmResponse.java new file mode 100644 index 0000000..57e65fa --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/PdmResponse.java @@ -0,0 +1,21 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import jakarta.persistence.*; + +@Entity +public class PdmResponse extends ServiceResponse { + + public PdmResponse(){ + // Empty Constructor to satisfy JPA requirement + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/Person.java b/web/src/main/java/org/mitre/iwp/web/model/Person.java new file mode 100644 index 0000000..5c259c1 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/Person.java @@ -0,0 +1,102 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import org.apache.commons.lang3.StringUtils; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.*; + +import java.util.Collection; +import java.util.ArrayList; + +@Entity +public class Person { + public Person(){ + + } + + public Person(Long id, String name, String title, String department, String address, String email, String phone, Collection cases) { + this.id = id; + this.name = name; + this.title = title; + this.department = department; + this.address = address; + this.email = email; + this.phone = phone; + this.cases = cases; + } + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String name; + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + private String title; + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + + private String department; + public String getDepartment() { return department; } + public void setDepartment(String department) { this.department = department; } + + private String address; + public String getAddress() { return address; } + public void setAddress(String address) { this.address = address; } + + private String email; + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + + private String phone; + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } + + @OneToMany(cascade = CascadeType.ALL, + orphanRemoval = true) + private Collection cases = new ArrayList(); + + public Collection getCases() { + return new ArrayList(this.cases); + } + + public void addCaseInformation(CaseInformation caseInformation) { + if (this.cases.contains(caseInformation)) return; + + this.cases.add(caseInformation); + } + + public void removeCaseInformation(CaseInformation caseInformation) { + if (!this.cases.contains(caseInformation)) return; + + this.cases.remove(caseInformation); + } + + public boolean isPopulated() { + boolean populated; + + populated = StringUtils.isNotBlank(this.name); + populated |= StringUtils.isNotBlank(this.title); + populated |= StringUtils.isNotBlank(this.department); + populated |= StringUtils.isNotBlank(this.address); + populated |= StringUtils.isNotBlank(this.email); + populated |= StringUtils.isNotBlank(this.phone); + + return populated; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/Point.java b/web/src/main/java/org/mitre/iwp/web/model/Point.java new file mode 100644 index 0000000..56cf031 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/Point.java @@ -0,0 +1,52 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.google.gson.annotations.Expose; + +import jakarta.persistence.Embeddable; + +@Embeddable +@JsonIgnoreProperties(ignoreUnknown = true) +public class Point { + + public Point(double x, double y) { + this.x = x; + this.y = y; + } + + public Point() { + this.x = 0; + this.y = 0; + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + @Expose + private double x; + @Expose + private double y; +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/Polygon.java b/web/src/main/java/org/mitre/iwp/web/model/Polygon.java new file mode 100644 index 0000000..b8c8733 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/Polygon.java @@ -0,0 +1,42 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import com.google.gson.annotations.Expose; + +import java.util.ArrayList; +import java.util.List; + +public class Polygon { + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + @Expose + public String name; + public String getName(){return this.name; } + public void setName(String name) { this.name = name; } + + @Expose + public String code; + public String getCode(){ return this.code;} + public void setCode(String code){this.code = code;} + + @Expose + public String color; + public String getColor() { return this.color; } + public void setColor(String color) { this.color = color; } + + @Expose + public List outline = new ArrayList<>(); + public List getOutline(){ return this.outline; } + public void setOutline(List points){ this.outline = points; } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/Privilege.java b/web/src/main/java/org/mitre/iwp/web/model/Privilege.java new file mode 100644 index 0000000..3f40322 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/Privilege.java @@ -0,0 +1,76 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import com.google.common.base.Objects; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +@Entity +public class Privilege { + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + public Privilege(){ + + } + + public Privilege(String name, String description){ + this.name = name; + this.description = description; + } + + public Privilege(Long id, String name, String description) { + this(name, description); + this.id = id; + } + + private String name; + public String getName() { return this.name; } + public void setName(String name) { this.name = name; } + + private String description; + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Privilege priv = (Privilege) obj; + return this.equals(priv); + } + + @Override + public int hashCode(){ + return Objects.hashCode(id, name, description); + } + + @Override + public String toString() { + return "Privilege [name=" + name + "]" + "[id=" + id + "]"; + } + + + +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/Probe.java b/web/src/main/java/org/mitre/iwp/web/model/Probe.java new file mode 100644 index 0000000..2bdc4da --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/Probe.java @@ -0,0 +1,117 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class Probe { + private static final Logger log = LoggerFactory.getLogger(Probe.class); + + public Probe(){ + + } + + public Probe(Long id, String transactionControlReference, String transactionControlNumber, List imageList, String name, String subjectIdentifier) { + this.id = id; + this.transactionControlReference = transactionControlReference; + this.transactionControlNumber = transactionControlNumber; + this.imageList = imageList; + this.name = name; + this.subjectIdentifier = subjectIdentifier; + } + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String transactionControlReference; + public String getTransactionControlReference() { return this.transactionControlReference; } + public void setTransactionControlReference(String tcr) { this.transactionControlReference = tcr; } + + private String transactionControlNumber; + public String getTransactionControlNumber() { return this.transactionControlNumber; } + public void setTransactionControlNumber(String tcn) { this.transactionControlNumber = tcn; } + + @OneToMany(cascade = { CascadeType.MERGE, CascadeType.REFRESH, CascadeType.REMOVE }, + orphanRemoval = true) + private List imageList = new ArrayList<>(); + public List getImageList() { return this.imageList; } + public void setImageList(List image) { + this.imageList = image; + } + + public void addImage(ImageData imageData) { + this.imageList.add(imageData); + } + + public void removeImage(ImageData imageData) { + if (this.imageList.contains(imageData)) { + this.imageList.remove(imageData); + } + } + + + + private String name; + public String getName() { + return this.name; + } + public void setName(String name) { + this.name = name; + } + + private String subjectIdentifier; + public String getSubjectIdentifier() { + return this.subjectIdentifier; + } + public void setSubjectIdentifier(String subjectIdentifier) { + this.subjectIdentifier = subjectIdentifier; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + String newLineString = System.getProperty("line.separator"); + + result.append(this.getClass().getName()); + result.append(" Object {"); + result.append(newLineString); + + //determine fields declared in this class only (no fields of superclass) + java.lang.reflect.Field[] fields = this.getClass().getDeclaredFields(); + + //print field names paired with their values + for (java.lang.reflect.Field field : fields) { + result.append(" "); + try { + result.append(field.getName()); + result.append(": "); + //requires access to private field: + result.append(field.get(this)); + } + catch (IllegalAccessException ex) { + log.error(ex.getMessage(), ex); + } + result.append(newLineString); + } + result.append("}"); + + return result.toString(); + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/Profile.java b/web/src/main/java/org/mitre/iwp/web/model/Profile.java new file mode 100644 index 0000000..a79083e --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/Profile.java @@ -0,0 +1,97 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import jakarta.persistence.*; + +@Entity +public class Profile { + public Profile(){ + + } + + public Profile(Long id, String name, String destinationAgencyIdentifier, String originatingAgencyIdentifier, String attentionIndicator, + String sourceAgency, boolean isReportExclude, String fullName, String email, String phoneNumber, String title, + String department, String address, UserSetting userSetting){ + this.id = id; + this.name = name; + this.destinationAgencyIdentifier = destinationAgencyIdentifier; + this.originatingAgencyIdentifier = originatingAgencyIdentifier; + this.attentionIndicator = attentionIndicator; + this.sourceAgency = sourceAgency; + this.isReportExclude = isReportExclude; + this.fullName = fullName; + this.email = email; + this.phoneNumber = phoneNumber; + this.title = title; + this.department = department; + this.address = address; + this.userSetting = userSetting; + } + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String name; + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + private String destinationAgencyIdentifier; + public String getDestinationAgencyIdentifier() { return destinationAgencyIdentifier; } + public void setDestinationAgencyIdentifier(String destinationAgencyIdentifier) { this.destinationAgencyIdentifier = destinationAgencyIdentifier; } + + private String originatingAgencyIdentifier; + public String getOriginatingAgencyIdentifier() { return originatingAgencyIdentifier; } + public void setOriginatingAgencyIdentifier(String originatingAgencyIdentifier) {this.originatingAgencyIdentifier = originatingAgencyIdentifier; } + + private String attentionIndicator; + public String getAttentionIndicator() { return attentionIndicator; } + public void setAttentionIndicator(String attentionIndicator) { this.attentionIndicator = attentionIndicator; } + + private String sourceAgency; + public String getSourceAgency() { return sourceAgency; } + public void setSourceAgency(String sourceAgency) { this.sourceAgency = sourceAgency; } + + private boolean isReportExclude; + public boolean getIsReportExclude() { return isReportExclude; } + public void setIsReportExclude(boolean reportExclude) { isReportExclude = reportExclude; } + + private String fullName; + public String getFullName() { return fullName; } + public void setFullName(String fullName) { this.fullName = fullName; } + + private String email; + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + + private String phoneNumber; + public String getPhoneNumber() { return phoneNumber; } + public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } + + private String title; + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + + private String department; + public String getDepartment() { return department; } + public void setDepartment(String department) { this.department = department; } + + private String address; + public String getAddress() { return address; } + public void setAddress(String address) { this.address = address; } + + @OneToOne(cascade = CascadeType.ALL ) + private UserSetting userSetting; + public UserSetting getUserSetting() { return this.userSetting; } + public void setUserSetting(UserSetting userSetting) { this.userSetting = userSetting; } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/ReportData.java b/web/src/main/java/org/mitre/iwp/web/model/ReportData.java new file mode 100644 index 0000000..721848e --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/ReportData.java @@ -0,0 +1,200 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class ReportData { + private static final Logger log = LoggerFactory.getLogger(ReportData.class); + + public ReportData(){ + + } + + public ReportData(Long id, + String fileName, + Probe probe, + List candidates, + boolean includeExclude, + ResponseType responseType, + CaseInformation caseInformation, + List results, + List invalidFields, + List errorMessages, + UserAuthentication createdBy, + UserAuthentication workedBy, + Boolean isFinished, + Boolean allowNullRequester) { + this.id = id; + this.fileName = fileName; + this.probe = probe; + this.candidates = candidates; + this.includeExclude = includeExclude; + this.responseType = responseType; + this.caseInformation = caseInformation; + this.results = results; + this.invalidFields = invalidFields; + this.errorMessages = errorMessages; + this.createdBy = createdBy; + this.workedBy = workedBy; + this.isFinished = isFinished; + this.allowNullRequester = allowNullRequester; + } + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + public boolean isValid() { + if(probe == null) { + invalidFields.add("Missing Probe Field"); + } else { + if (probe.getImageList().isEmpty()) { + invalidFields.add("Missing Probe Image Field"); + } else { + + for (ImageData data : probe.getImageList()) { + if (data.getId() == null) { + invalidFields.add("Missing Probe Image Id"); + } + } + } + } + + if(candidates == null) { + invalidFields.add("Missing Candidate Field"); + } else { + if (this.candidates.isEmpty()) { + invalidFields.add("Missing Candidate List"); + } + } + + return invalidFields.isEmpty(); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + String newLineString = System.getProperty("line.separator"); + + result.append(this.getClass().getName()); + result.append(" Object {"); + result.append(newLineString); + + //determine fields declared in this class only (no fields of superclass) + java.lang.reflect.Field[] fields = this.getClass().getDeclaredFields(); + + //print field names paired with their values + for (java.lang.reflect.Field field : fields) { + result.append(" "); + try { + result.append(field.getName()); + result.append(": "); + //requires access to private field: + result.append(field.get(this)); + } + catch (IllegalAccessException ex) { + log.error(ex.getMessage(), ex); + } + result.append(newLineString); + } + result.append("}"); + + return result.toString(); + } + + private String fileName; + public String getFileName() { return this.fileName; } + public void setFileName(String fileName) { this.fileName = fileName; } + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private Probe probe = new Probe(); + public Probe getProbe() { return this.probe; } + public void setProbe(Probe probe) { this.probe = probe; } + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + private List candidates = new ArrayList<>(); + public List getCandidates() { return this.candidates; } + public void setCandidates(List candidates) { this.candidates = candidates; } + + private boolean includeExclude = false; + public boolean getIncludeExclude() { return this.includeExclude; } + public void setIncludeExclude(boolean includeExclude) { this.includeExclude = includeExclude; } + + @Enumerated(value = EnumType.STRING) + private ResponseType responseType = ResponseType.Unknown; + public ResponseType getResponseType() { return this.responseType; } + public void setResponseType(ResponseType rType) { this.responseType = rType; } + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private CaseInformation caseInformation = new CaseInformation(); + public CaseInformation getCaseInformation() { return caseInformation; } + public void setCaseInformation(CaseInformation caseInformation) { this.caseInformation = caseInformation; } + + @ElementCollection + private List results = new ArrayList<>(); + public List getResults() { return this.results; } + public void setResults(List results) { this.results = results; } + + @ElementCollection + private List invalidFields = new ArrayList<>(); + public List getInvalidFields() { return this.invalidFields; } + public void setInvalidFields(List invFields) { this.invalidFields = invFields; } + + @ElementCollection + private List errorMessages = new ArrayList<>(); + public List getErrorMessages() { return errorMessages; } + public void addErrorMessages(String errorMessage) { this.errorMessages.add(errorMessage); } + + @ManyToOne + private UserAuthentication createdBy; + + public UserAuthentication getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(UserAuthentication createdBy) { + this.createdBy = createdBy; + } + + @ManyToOne + private UserAuthentication workedBy; + + public UserAuthentication getWorkedBy() { + return workedBy; + } + + public void setWorkedBy(UserAuthentication workedBy) { + this.workedBy = workedBy; + } + + public Long getUserId() { + return createdBy != null ? createdBy.getId() : null; + } + + + private Boolean isFinished = false; + public void setIsFinished(Boolean isFinished) { this.isFinished = isFinished; } + public Boolean getIsFinished() { return this.isFinished; } + + + private Boolean allowNullRequester; + public void setAllowNullRequester(Boolean allowNullRequester) {this.allowNullRequester = allowNullRequester;} + public Boolean getAllowNullRequester() { return this.allowNullRequester; } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/ReportResponse.java b/web/src/main/java/org/mitre/iwp/web/model/ReportResponse.java new file mode 100644 index 0000000..a41aaf2 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/ReportResponse.java @@ -0,0 +1,37 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +@Entity +public class ReportResponse { + private Long userId; + private String filePath; + private Long reportDataId; + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + public Long getUserId(){ return this.userId; } + public void setUserId(Long userId) { this.userId = userId; } + + public Long getReportDataId(){ return this.reportDataId; } + public void setReportDataId(Long reportDataId) { this.reportDataId = reportDataId; } + + public String getFilePath() { return this.filePath; } + public void setFilePath(String filePath) { this.filePath = filePath; } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/ResponseType.java b/web/src/main/java/org/mitre/iwp/web/model/ResponseType.java new file mode 100644 index 0000000..da583c0 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/ResponseType.java @@ -0,0 +1,43 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import java.util.HashMap; +import java.util.Map; + +public enum ResponseType { + Unknown(0), + SRB(1), + ERRB(2), + XML(3), + Image(4); + + private int value; + private static Map map = new HashMap<>(); + + ResponseType(int value){ + this.value = value; + } + + static { + for(ResponseType label : ResponseType.values()){ + map.put(label.value, label); + } + } + + public static ResponseType valueOf(int label) { + return map.get(label); + } + + public int getValue() { + return value; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/ReviewCase.java b/web/src/main/java/org/mitre/iwp/web/model/ReviewCase.java new file mode 100644 index 0000000..96edfd2 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/ReviewCase.java @@ -0,0 +1,96 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import org.springframework.format.annotation.DateTimeFormat; + +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import java.util.Date; +import java.util.List; + +@Entity +public class ReviewCase { + public ReviewCase(){ + + } + + public ReviewCase(Long id, String title, String description, String priority, Date dueDate, Date createdDate, String createdBy, Date modifiedDate, String modifiedBy, List tags, boolean isWholeCaseCompleted) { + this.id = id; + this.title = title; + this.description = description; + this.priority = priority; + this.dueDate = dueDate; + this.createdDate = createdDate; + this.createdBy = createdBy; + this.modifiedDate = modifiedDate; + this.modifiedBy = modifiedBy; + this.tags = tags; + this.isWholeCaseCompleted = isWholeCaseCompleted; + } + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String title; + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + + private String description; + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + + private String priority; + public String getPriority() { return priority; } + public void setPriority(String priority) { this.priority = priority; } + + @DateTimeFormat(pattern = "yyyy-MM-dd") + @JsonFormat(shape= JsonFormat.Shape.STRING, pattern="yyyy-MM-dd", timezone="GMT") + public Date dueDate; + public Date getDueDate() { return dueDate; } + public void setDueDate(Date dueDate) { this.dueDate = dueDate; } + + @DateTimeFormat(pattern = "yyyy-MM-dd") + @JsonFormat(shape= JsonFormat.Shape.STRING, pattern="yyyy-MM-dd", timezone="GMT") + public Date createdDate; + public Date getCreatedDate() { return createdDate; } + public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; } + + private String createdBy; + public String getCreatedBy() { return createdBy; } + public void setCreatedBy(String createdBy) { this.createdBy = createdBy; } + + @DateTimeFormat(pattern = "yyyy-MM-dd") + @JsonFormat(shape= JsonFormat.Shape.STRING, pattern="yyyy-MM-dd", timezone="GMT") + public Date modifiedDate; + public Date getModifiedDate() { return modifiedDate; } + public void setModifiedDate(Date modifiedDate) { this.modifiedDate = modifiedDate; } + + private String modifiedBy; + public String getModifiedBy() { return modifiedBy; } + public void setModifiedBy(String modifiedBy) { this.modifiedBy = modifiedBy; } + + @ElementCollection + public List tags; + public List getTags() { return tags; } + public void setTags(List tags) { this.tags = tags; } + + private boolean isWholeCaseCompleted; + public boolean getIsWholeCaseCompleted() { return isWholeCaseCompleted; } + public void setIsWholeCaseCompleted(boolean isWholeCaseCompleted) { this.isWholeCaseCompleted = isWholeCaseCompleted; } + +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/Service.java b/web/src/main/java/org/mitre/iwp/web/model/Service.java new file mode 100644 index 0000000..1ea680c --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/Service.java @@ -0,0 +1,34 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +public class Service { + private String serviceName; + private String requestQueue; + private String responseQueue; + private boolean serviceEnabled; + private boolean serviceExcluded; + + public String getServiceName() { return serviceName; } + public void setServiceName(String serviceName) { this.serviceName = serviceName; } + + public String getRequestQueue() { return requestQueue; } + public void setRequestQueue(String requestQueue) { this.requestQueue = requestQueue; } + + public String getResponseQueue() { return responseQueue; } + public void setResponseQueue(String responseQueue) { this.responseQueue = responseQueue; } + + public Boolean getServiceEnabled() { return serviceEnabled; } + public void setServiceEnabled(Boolean serviceEnabled) { this.serviceEnabled = serviceEnabled; } + + public Boolean getServiceExcluded() { return serviceExcluded; } + public void setServiceExcluded(Boolean serviceExcluded) { this.serviceExcluded = serviceExcluded; } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/ServiceCallStatus.java b/web/src/main/java/org/mitre/iwp/web/model/ServiceCallStatus.java new file mode 100644 index 0000000..53780b8 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/ServiceCallStatus.java @@ -0,0 +1,18 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +public enum ServiceCallStatus { + NotSent, + Pending, + Error, + Received +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/ServiceResponse.java b/web/src/main/java/org/mitre/iwp/web/model/ServiceResponse.java new file mode 100644 index 0000000..a6cf3f3 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/ServiceResponse.java @@ -0,0 +1,48 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import com.google.gson.annotations.Expose; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; + +@MappedSuperclass +public class ServiceResponse { + @Id + @GeneratedValue + private Long imageId; + public Long getImageId() { return this.imageId; } + public void setImageId(Long imageId) { this.imageId = imageId; } + + public void setError(String error) { + this.error = error; + } + public String getError() { + return error; + } + + public void setStatus(ServiceCallStatus status) { this.status = status; } + public ServiceCallStatus getStatus() { return this.status; } + + public void setEbtsImageId(Long ebtsImageId) { this.ebtsImageId = ebtsImageId; } + public Long getEbtsImageId() { return this.ebtsImageId; } + + @Expose + String error; + @Expose + String imagePath; + @Expose + ServiceCallStatus status; + @Expose + Long ebtsImageId; +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/State.java b/web/src/main/java/org/mitre/iwp/web/model/State.java new file mode 100644 index 0000000..4d1c0e0 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/State.java @@ -0,0 +1,46 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import jakarta.persistence.*; +import java.util.Map; + +@Entity +public class State { + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + @OneToOne(orphanRemoval = true) + private Annotation annotation; + public Annotation getAnnotation() { return annotation; } + public void setAnnotation(Annotation annotation) { + this.annotation = annotation; + } + + @ElementCollection + @MapKeyColumn(name="name") + @Column(name="value") + @CollectionTable(name="state_data_map", joinColumns={@JoinColumn(name="state_data_id", referencedColumnName = "id")}) + private Map data; + public Map getData() { return data; } + public void setData(Map data) { + this.data = data; + } + + private int type; + public int getType() { return type; } + public void setType(int type) { + this.type = type; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/TshepiiCompareResponse.java b/web/src/main/java/org/mitre/iwp/web/model/TshepiiCompareResponse.java new file mode 100644 index 0000000..86a7a95 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/TshepiiCompareResponse.java @@ -0,0 +1,97 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import com.google.gson.annotations.Expose; +import com.google.protobuf.InvalidProtocolBufferException; +import org.mitre.iwp.buffers.Tshepii; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.*; + +@Entity +public class TshepiiCompareResponse { + private static final Logger log = LoggerFactory.getLogger(TshepiiCompareResponse.class); + @Id + private Long imageId; + public Long getImageId() { return this.imageId; } + public void setImageId(Long imageId) { this.imageId = imageId; } + + public void setError(String error) { + this.error = error; + } + public String getError() { + return error; + } + + public void setStatus(ServiceCallStatus status) { this.status = status; } + public ServiceCallStatus getStatus() { return this.status; } + + public void setEbtsImageId(Long ebtsImageId) { this.ebtsImageId = ebtsImageId; } + public Long getEbtsImageId() { return this.ebtsImageId; } + + + + private Long candidateImageId; + + public void setCandidateImageId(Long candidateImageId) { this.candidateImageId = candidateImageId; } + public Long getCandidateImageId() { return this.candidateImageId; } + + private Long candidateEbtsImageId; + public void setCandidateEbtsImageId(Long candidateEbtsImageId) { this.candidateEbtsImageId = candidateEbtsImageId; } + public Long getCandidateEbtsImageId() { return this.candidateEbtsImageId; } + + @Lob + @Column(columnDefinition="LONGBLOB") + private byte[] serviceResponse; + public void setServiceResponse(byte[] response) { this.serviceResponse = response; } + public byte[] getServiceResponse() { return this.serviceResponse; } + + @Transient + @Expose + private TshepiiResponse probeResponse; + + @Transient + @Expose + private TshepiiResponse candidateResponse; + + @Expose + String error; + @Expose + String imagePath; + @Expose + ServiceCallStatus status; + @Expose + Long ebtsImageId; + + public boolean buildFromServiceResponse(){ + if(serviceResponse.length == 0){ + log.info("Tshepii Compare Response ServiceResponse length is 0"); + return false; + } + + try { + Tshepii.TShepiiCompareResponse serviceMessage = Tshepii.TShepiiCompareResponse.parseFrom(serviceResponse); + + this.probeResponse = new TshepiiResponse(); + this.probeResponse.buildFromCryptResponse(serviceMessage.getCryptResponse()); + + this.candidateResponse = new TshepiiResponse(); + this.candidateResponse.buildFromCryptResponse(serviceMessage.getCryptResponse2()); + + } catch (InvalidProtocolBufferException e) { + log.info(e.getMessage()); + return false; + } + return true; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/TshepiiResponse.java b/web/src/main/java/org/mitre/iwp/web/model/TshepiiResponse.java new file mode 100644 index 0000000..36e4e5c --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/TshepiiResponse.java @@ -0,0 +1,163 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import com.google.gson.annotations.Expose; +import org.mitre.iwp.buffers.Tshepii; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class TshepiiResponse extends ServiceResponse { + private static final Logger log = LoggerFactory.getLogger(TshepiiResponse.class); + public TshepiiResponse(){ + // Empty Constructor to satisfy JPA requirement + } + + @Lob + @Column(columnDefinition="LONGBLOB") + private byte[] serviceResponse; + public void setServiceResponse(byte[] response) { this.serviceResponse = response; } + public byte[] getServiceResponse() { return this.serviceResponse; } + + @Transient + @Expose + private Point cropCenter; + + @Transient + @Expose + private Point originalIrisCenter; + + @Transient + @Expose + private Point originalPupilCenter; + + @Transient + @Expose + private double cropRadius = 0; + + @Transient + @Expose + private double originalIrisRadius = 0; + + @Transient + @Expose + private double originalPupilRadius = 0; + + @Transient + @Expose + private List cryptList = new ArrayList<>(); + public List getCryptList() { return this.cryptList; } + public void setCryptList(List cryptList) { this.cryptList = cryptList; } + + @Transient + @Expose + private List keyPoints = new ArrayList<>(); + + @Transient + public List getKeyPoints() { + return this.keyPoints; + } + + @Transient + @Expose + private List matchIndex = new ArrayList<>(); + + public Point getCropCenter() { + return cropCenter; + } + + public void setCropCenter(Point center) { this.cropCenter = center; } + public double getCropRadius(){ + return this.cropRadius; + } + public void setCropRadius(double radius){ + this.cropRadius = radius; + } + + public Point getOriginalIrisCenter() { + return this.originalIrisCenter; + } + public void setOriginalIrisCenter(Point center) { this.originalIrisCenter = center; } + + public double getOriginalIrisRadius(){ + return this.originalIrisRadius; + } + public void setOriginalIrisRadius(double radius){ + this.originalIrisRadius = radius; + } + + public Point getOriginalPupilCenter() { return originalPupilCenter; } + public void setOriginalPupilCenter(Point center) { this.originalPupilCenter = center; } + + public double getOriginalPupilRadius(){ + return this.originalPupilRadius; + } + public void setOriginalPupilRadius(double radius){ + this.originalPupilRadius = radius; + } + + public List getMatchIndex() { return this.matchIndex; } + + public int getCryptCount(){ + return this.cryptList.size(); + } + + public List getCryptAt(int index) { + if(index < 0 || index >= this.cryptList.size()) + return new ArrayList<>(); + + Crypt crypt = this.cryptList.get(index); + return crypt.getPointList(); + } + + public void buildFromCryptResponse(Tshepii.TShepiiResponse tshepiiServiceResponse){ + + if(tshepiiServiceResponse == null) { + log.info("Tshepii Protobuff did not properly parse. Unknown reason"); + return; + } + + this.setEbtsImageId(Long.parseLong(tshepiiServiceResponse.getImageId())); + + this.setCropCenter(new Point(tshepiiServiceResponse.getCropIris().getCenter().getX(), tshepiiServiceResponse.getCropIris().getCenter().getY())); + this.setCropRadius(tshepiiServiceResponse.getCropIris().getRadius()); + + this.setOriginalIrisCenter(new Point(tshepiiServiceResponse.getOriginalIris().getCenter().getX(), tshepiiServiceResponse.getOriginalIris().getCenter().getY())); + this.setOriginalIrisRadius(tshepiiServiceResponse.getOriginalIris().getRadius()); + + this.setOriginalPupilCenter(new Point(tshepiiServiceResponse.getOriginalPupil().getCenter().getX(), tshepiiServiceResponse.getOriginalPupil().getCenter().getY())); + this.setOriginalPupilRadius(tshepiiServiceResponse.getOriginalPupil().getRadius()); + + for(Tshepii.Crypt cryptListItem : tshepiiServiceResponse.getCryptsList()){ + List pointList = new ArrayList<>(); + + for(Tshepii.TPoint cryptPoint : cryptListItem.getPointsList()){ + pointList.add(new Point(cryptPoint.getX(), cryptPoint.getY())); + } + + Crypt crypt = new Crypt(pointList); + this.cryptList.add(crypt); + } + + for(int matchItem : tshepiiServiceResponse.getMatchIndexList()){ + this.getMatchIndex().add(matchItem); + } + + for(Tshepii.TPoint tpoint : tshepiiServiceResponse.getKeypointsList()){ + this.keyPoints.add(new Point(tpoint.getX(), tpoint.getY())); + } + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/UserAuthentication.java b/web/src/main/java/org/mitre/iwp/web/model/UserAuthentication.java new file mode 100644 index 0000000..0a68e30 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/UserAuthentication.java @@ -0,0 +1,88 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; + +import jakarta.persistence.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +public class UserAuthentication { + public UserAuthentication(){ + + } + + public UserAuthentication(Long id, String username, String password, List roles, Profile profile, boolean isNewUser){ + this.id = id; + this.username = username; + this.password = password; + this.roles = roles; + this.profile = profile; + this.isNewUser = isNewUser; + } + + @Id + @GeneratedValue + private Long id; + public Long getId() { + return this.id; + } + public void setId(Long id) { + this.id = id; + } + + private String username; + public String getUsername() { + return this.username; + } + public void setUsername(String userName) { + this.username = userName; + } + + @JsonProperty(access = Access.WRITE_ONLY) + private String password; + public String getPassword() { + return this.password; + } + public void setPassword(String password) { + this.password = password; + } + + @ElementCollection(fetch = FetchType.EAGER) + private List roles = new ArrayList<>(); + public List getRoles() { + return this.roles; + } + public void setRoles(List roles) { + this.roles = roles; + } + + @OneToOne(cascade = CascadeType.ALL) + private Profile profile; + public Profile getProfile() { + return this.profile; + } + public void setProfile(Profile profile) { + this.profile = profile; + } + + private boolean isNewUser; + public boolean getIsNewUser() { + return this.isNewUser; + } + public void setIsNewUser(boolean isNewUser) { + this.isNewUser = isNewUser; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/UserRole.java b/web/src/main/java/org/mitre/iwp/web/model/UserRole.java new file mode 100644 index 0000000..41f13b0 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/UserRole.java @@ -0,0 +1,97 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import jakarta.persistence.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +@Entity +public class UserRole { + public UserRole(){ + + } + + public UserRole(Long id, String name, String description, Collection users, Collection privileges){ + this.id = id; + this.name = name; + this.description = description; + this.users = users; + this.privileges = privileges; + } + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private String name; + public String getName() { return this.name; } + public void setName(String name) { this.name = name; } + + private String description; + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + + @ManyToMany + private Collection users = new ArrayList<>(); + public Collection getUsers() {return users;} + public void setUsers(Collection users) {this.users = users;} + public void addUser(UserAuthentication user) { this.users.add(user); } + + @ManyToMany + private Collection privileges = new ArrayList<>(); + public Collection getPrivileges() {return privileges;} + public void setPrivileges(Collection privileges) { this.privileges = privileges; } + public void addPrivilege(Privilege privilege) { this.privileges.add(privilege); } + public void removePrivilege(long privId) { + this.privileges.removeIf(n-> (n.getId() == privId)); + } + + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final UserRole role = (UserRole) obj; + return this.equals(role); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, description, users, privileges); + } + + @Override + public String toString() { + return "Role [name=" + name + "]" + "[id=" + id + "]"; + } + + public List getPrivilegeList(){ + List list = new ArrayList<>(); + for (Privilege p : privileges) { + list.add(p.getName()); + } + return list; + } + +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/UserSetting.java b/web/src/main/java/org/mitre/iwp/web/model/UserSetting.java new file mode 100644 index 0000000..445db96 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/UserSetting.java @@ -0,0 +1,62 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +@Entity +public class UserSetting { + public UserSetting(){ + } + + public UserSetting(Long id, int strokeWidth, String drawColor, String fillColor, int numHistory, int tshepiiStrokeOpacity){ + this.id = id; + this.strokeWidth = strokeWidth; + this.drawColor = drawColor; + this.fillColor = fillColor; + this.numHistory = numHistory; + this.tshepiiStrokeOpacity = tshepiiStrokeOpacity; + } + + @Id + @GeneratedValue + private Long id; + public Long getId() { return this.id; } + public void setId(Long id) { this.id = id; } + + private int strokeWidth; + public int getStrokeWidth() { return strokeWidth; } + public void setStrokeWidth(int strokeWidth) { this.strokeWidth = strokeWidth; } + + private String drawColor; + public String getDrawColor() { return drawColor; } + public void setDrawColor(String drawColor) { + this.drawColor = drawColor; + } + + private String fillColor; + public String getFillColor() { return fillColor; } + public void setFillColor(String fillColor) { + this.fillColor = fillColor; + } + + private int numHistory; + public int getNumHistory() { return numHistory; } + public void setNumHistory(int numHistory) { + this.numHistory = numHistory; + } + + private int tshepiiStrokeOpacity; + public int getTshepiiStrokeOpacity() { return tshepiiStrokeOpacity; } + public void setTshepiiStrokeOpacity(int tshepiiStrokeOpacity) { this.tshepiiStrokeOpacity = tshepiiStrokeOpacity; } +} diff --git a/web/src/main/java/org/mitre/iwp/web/model/VersionInfo.java b/web/src/main/java/org/mitre/iwp/web/model/VersionInfo.java new file mode 100644 index 0000000..23af4e7 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/model/VersionInfo.java @@ -0,0 +1,39 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.model; + +import java.util.HashMap; +import java.util.Map; + +public class VersionInfo { + public String getIwpVersion() { + return iwpVersion; + } + public void setIwpVersion(String iwpVersion) { + this.iwpVersion = iwpVersion; + } + public String getUiVersion() { + return uiVersion; + } + public void setUiVersion(String uiVersion) { + this.uiVersion = uiVersion; + } + public Map getServiceVersions() { + return serviceVersions; + } + public void setServiceVersions(Map serviceVersions) { + this.serviceVersions = serviceVersions; + } + + private String iwpVersion = ""; + private String uiVersion = ""; + private Map serviceVersions = new HashMap<>(); +} diff --git a/web/src/main/java/org/mitre/iwp/web/reporting/ImageReporting.java b/web/src/main/java/org/mitre/iwp/web/reporting/ImageReporting.java new file mode 100644 index 0000000..09d34f6 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/reporting/ImageReporting.java @@ -0,0 +1,168 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.reporting; + +import com.itextpdf.text.*; +import com.itextpdf.text.pdf.PdfPCell; +import com.itextpdf.text.pdf.PdfPTable; +import com.itextpdf.text.pdf.PdfWriter; +import org.mitre.iwp.web.data.EbtsImage; +import org.mitre.iwp.web.exception.InvalidMessageException; +import org.mitre.iwp.web.model.Candidate; +import org.mitre.iwp.web.model.ImageData; +import org.mitre.iwp.web.model.Probe; +import org.mitre.iwp.web.model.ReportData; +import org.mitre.iwp.web.service.EbtsImageRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Collections; + +@Service +public class ImageReporting { + private static final Logger log = LoggerFactory.getLogger(ImageReporting.class); + + private final EbtsImageRepository imageRepository; + + private static final String notFoundMessage = "Image data not found"; + + public ImageReporting(EbtsImageRepository imageRepository) { + this.imageRepository = imageRepository; + } + + public byte[] createPdfReport(ReportData reportData) throws DocumentException, InvalidMessageException, IOException { + log.info("Generating report for id: {}", reportData.getId()); + if(ReportingUtil.isValid(reportData)) { + + Font headerFont = FontFactory.getFont(FontFactory.HELVETICA, 16, Font.BOLDITALIC); + headerFont.setColor(BaseColor.WHITE); + + Font subHeaderFont = FontFactory.getFont(FontFactory.HELVETICA, 14, Font.BOLDITALIC); + subHeaderFont.setColor(BaseColor.BLACK); + + Probe probe = reportData.getProbe(); + Candidate candidate = reportData.getCandidates().get(0); + + if (probe.getImageList().size() != 1) { + log.info("Probe Image List is does not equal 1. Returning"); + return new byte[0]; + } + + if (candidate.getImageList().size() != 1) { + log.info("Candidate Image List is does not equal 1. Returning"); + return new byte[0]; + } + + ImageData probeImage = probe.getImageList().get(0); + ImageData candidateImage = candidate.getImageList().get(0); + + Document document = new Document(PageSize.A4, 25, 25, 25, 25); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PdfWriter.getInstance(document, out); + + document.open(); + + ReportingUtil.handleCaseInformation(reportData, document); + + PdfPTable comparisonTable = new PdfPTable(2); + comparisonTable.setWidthPercentage(100); + comparisonTable.setSpacingBefore(ReportingUtil.tableSpacing); + comparisonTable.setSpacingAfter(ReportingUtil.tableSpacing); + comparisonTable.setSpacingBefore(ReportingUtil.tableSpacing); + comparisonTable.setSpacingAfter(ReportingUtil.tableSpacing); + + PdfPCell header = new PdfPCell(); + String probeIdentifier = probe.getSubjectIdentifier(); + String candidateIdentifier = candidate.getSubjectIdentifier(); + header.setColspan(2); + header.setPadding(ReportingUtil.cellPadding); + header.setBackgroundColor(BaseColor.BLACK); + header.setBorderWidth(ReportingUtil.cellBorder); + header.setPhrase(new Phrase(String.format("Comparison of %s and %s", (probeIdentifier == null || probeIdentifier.trim().isEmpty()) ? "PROBE" : probeIdentifier , (candidateIdentifier == null || candidateIdentifier.trim().isEmpty()) ? "CANDIDATE" : candidateIdentifier), headerFont)); + + comparisonTable.addCell(header); + + PdfPCell resultCell = new PdfPCell(); + resultCell.setColspan(2); + resultCell.setPadding(ReportingUtil.cellPadding); + resultCell.setPadding(ReportingUtil.cellPadding); + resultCell.setBackgroundColor(BaseColor.GRAY); + resultCell.setBorderWidth(ReportingUtil.cellBorder); + resultCell.setPhrase(new Phrase(candidate.getAdjudicationResults(), subHeaderFont)); + comparisonTable.addCell(resultCell); + + probeImage.setImageBytes(this.getImageBytes(probeImage)); + candidateImage.setImageBytes(this.getImageBytes(candidateImage)); + + if (probeImage.getUnrollImageId() != null) { + probeImage.setUnrollImageBytes(this.getImageBytes(probeImage.getUnrollImageId())); + } + + if (candidateImage.getUnrollImageId() != null) { + candidateImage.setUnrollImageBytes(this.getImageBytes(candidateImage.getUnrollImageId())); + } + + // Build Eye Comparison + ReportingUtil.addEyeComparison(comparisonTable, probeImage, candidateImage, candidate.getNote()); + + document.add(ReportingUtil.getNewDocumentParagraph(comparisonTable)); + document.newPage(); + + Paragraph paragraph = ReportingUtil.getNewDocumentParagraph(new Chunk("Appendix - Original Image Data", ReportingUtil.pageHeaderFont)); + paragraph.setSpacingAfter(10); + document.add(paragraph); + buildTable(probe.getImageList().get(0), "Image 1", document); + buildTable(candidate.getImageList().get(0), "Image 2", document); + + document.close(); + + return out.toByteArray(); + } + + log.info("Report is not valid"); + return new byte[0]; + } + + private byte[] getImageBytes(ImageData imageData) throws InvalidMessageException, IOException { + EbtsImage ebtsImage; + if( imageData.getRasterizeEbtsImageId() == null) { + ebtsImage = imageRepository.findById(imageData.getEbtsImageId()).orElseThrow(() -> new InvalidMessageException(notFoundMessage)); + } else { + ebtsImage = imageRepository.findById(imageData.getRasterizeEbtsImageId()).orElseThrow(() -> new InvalidMessageException(notFoundMessage)); + } + + return EbtsImage.getImageBytesPng(ebtsImage); + } + + private byte[] getImageBytes(long ebtsImageId) throws IOException, InvalidMessageException { + EbtsImage ebtsImage; + ebtsImage = imageRepository.findById(ebtsImageId).orElseThrow(() -> new InvalidMessageException(notFoundMessage)); + return EbtsImage.getImageBytesPng(ebtsImage); + } + + + private void buildTable(ImageData imageData, String header, Document document) throws InvalidMessageException, IOException, DocumentException { + PdfPTable table = ReportingUtil.createTableWithHeader(Collections.singletonList(header)); + table.setWidthPercentage(50); + table.setHorizontalAlignment(Element.ALIGN_LEFT); + table.setSpacingAfter(1); + table.setSpacingBefore(1); + EbtsImage ebtsImage = imageRepository.findById(imageData.getEbtsImageId()).orElseThrow(() -> new InvalidMessageException(notFoundMessage)); + table.addCell(ReportingUtil.buildImageCell(EbtsImage.getImageBytesPng(ebtsImage), true)); + table.addCell(ReportingUtil.buildTextCell(imageData.getImageNotes())); + + Paragraph paragraph = ReportingUtil.getNewDocumentParagraph(table); + document.add(paragraph); + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/reporting/MultiModalReporting.java b/web/src/main/java/org/mitre/iwp/web/reporting/MultiModalReporting.java new file mode 100644 index 0000000..88fd61f --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/reporting/MultiModalReporting.java @@ -0,0 +1,252 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.reporting; + +import com.itextpdf.text.*; +import com.itextpdf.text.pdf.PdfPCell; +import com.itextpdf.text.pdf.PdfPTable; +import com.itextpdf.text.pdf.PdfWriter; +import org.apache.commons.lang3.StringUtils; +import org.mitre.iwp.web.data.EbtsImage; +import org.mitre.iwp.web.exception.InvalidMessageException; +import org.mitre.iwp.web.model.Candidate; +import org.mitre.iwp.web.model.ImageData; +import org.mitre.iwp.web.model.Probe; +import org.mitre.iwp.web.model.ReportData; +import org.mitre.iwp.web.service.EbtsImageRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.util.*; +import java.util.List; + +@Service +public class MultiModalReporting { + private static final Logger log = LoggerFactory.getLogger(MultiModalReporting.class); + + private final EbtsImageRepository imageRepository; + + private static final String NOT_FOUND_MESSAGE = "Image data not found"; + + private Font boldFont; + + public MultiModalReporting(EbtsImageRepository ebtsImageRepository){ + this.imageRepository = ebtsImageRepository; + } + + public byte[] createPdfReport(ReportData reportData) throws DocumentException { + log.info("Generating report for id: {}", reportData.getId()); + if(ReportingUtil.isValid(reportData)) { + + boldFont = FontFactory.getFont(FontFactory.HELVETICA, 12, Font.BOLD); + boldFont.setColor(BaseColor.BLACK); + + // Create Pdf Document elements + Document document = new Document(PageSize.A4, 25, 25, 25, 25); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PdfWriter.getInstance(document, out); + + document.open(); + + // Parse Data + parseReportData(reportData, document); + + document.close(); + + return out.toByteArray(); + } + + return new byte[0]; + } + + private void parseReportData(ReportData reportData, Document document) { + Probe probe = reportData.getProbe(); + Candidate candidate = reportData.getCandidates().get(0); + + try { + // Parse Case Information (if applicable) + ReportingUtil.handleCaseInformation(reportData, document); + + // Add Comparison Metadata + Paragraph paragraph = ReportingUtil.getNewDocumentParagraph(buildComparisonMetadataTable(probe, candidate)); + document.add(paragraph); + + // Add Iris Image Comparison Table(s) + createIrisTables(document, probe, candidate); + } catch (com.itextpdf.text.DocumentException e){ + log.info(e.getMessage()); + } + + } + + private void createIrisTables(Document document, Probe probe, Candidate candidate) { + for(ImageData imageData : probe.getImageList()){ + if(imageData.getRecordType() == 17){ + addIrisImageTable(document, imageData, probe.getSubjectIdentifier()); + } + } + + for(ImageData imageData : candidate.getImageList()){ + if(imageData.getRecordType() == 17){ + addIrisImageTable(document, imageData, candidate.getSubjectIdentifier()); + } + } + } + + private void addIrisImageTable(Document document, ImageData imageData, String subjectIdentifier) { + if (imageData.getEbtsImageId() != null) { + + try { + document.newPage(); + String irisLabel = String.format("%s and %s eye", (subjectIdentifier == null || subjectIdentifier.trim().isEmpty()) ? "Subject" : subjectIdentifier, imageData.getEyeLabel()); + PdfPTable irisTable = new PdfPTable(1); + irisTable.setWidthPercentage(100); + irisTable.setSpacingBefore(ReportingUtil.tableSpacing); + irisTable.setSpacingAfter(ReportingUtil.tableSpacing); + + PdfPCell header = new PdfPCell(); + header.setColspan(1); + header.setPadding(ReportingUtil.cellPadding); + header.setBackgroundColor(BaseColor.BLACK); + header.setBorderWidth(ReportingUtil.cellBorder); + header.setPhrase(new Phrase(irisLabel, ReportingUtil.headerFont)); + irisTable.addCell(header); + + EbtsImage ebtsImage; + if (imageData.getRasterizeEbtsImageId() == null) { + ebtsImage = imageRepository.findById(imageData.getEbtsImageId()).orElseThrow(() -> new InvalidMessageException(NOT_FOUND_MESSAGE)); + } else { + ebtsImage = imageRepository.findById(imageData.getRasterizeEbtsImageId()).orElseThrow(() -> new InvalidMessageException(NOT_FOUND_MESSAGE)); + } + + if (null != ebtsImage) { + imageData.setImageBytes(EbtsImage.getImageBytesPng(ebtsImage)); + } else { + return; + } + + //get the unroll images + if (imageData.getUnrollImageId() != null) { + ebtsImage = imageRepository.findById(imageData.getUnrollImageId()).orElseThrow(() -> new InvalidMessageException(NOT_FOUND_MESSAGE)); + if (ebtsImage != null) + imageData.setUnrollImageBytes(EbtsImage.getImageBytesPng(ebtsImage)); + } + + com.itextpdf.text.List eyeAnnotation = new com.itextpdf.text.List(); + ReportingUtil.buildAnnotationOutput(String.format("%s Annotations", irisLabel), eyeAnnotation, imageData.getAnnotations()); + irisTable.addCell(ReportingUtil.buildListCell(eyeAnnotation)); + + if (StringUtils.isNotBlank(imageData.getImageData())) { + irisTable.addCell(ReportingUtil.buildImageCell( + ReportingUtil.drawImageAnnotationLabels(imageData.getImageData(), imageData.getAnnotations()), false)); + } else { + if (null != imageData.getImageBytes() && imageData.getImageBytes().length > 0) { + irisTable.addCell(ReportingUtil.buildImageCell( + ReportingUtil.drawImageAnnotationLabels(imageData.getImageBytes(), imageData.getAnnotations()), false)); + } else { + irisTable.addCell(ReportingUtil.buildTextCell("Error Building Image Cell.")); + } + } + + document.add(irisTable); + } catch (Exception e){ + log.info(e.getMessage()); + } + } + } + + private PdfPTable buildComparisonMetadataTable(Probe probe, Candidate candidate){ + PdfPTable table = new PdfPTable(3); + table.setSpacingBefore(ReportingUtil.tableSpacing); + table.setSpacingAfter(ReportingUtil.tableSpacing); + table.setWidthPercentage(100); + + PdfPCell header = new PdfPCell(); + header.setColspan(3); + header.setPadding(ReportingUtil.cellPadding); + header.setBackgroundColor(BaseColor.BLACK); + header.setBorderWidth(ReportingUtil.cellBorder); + header.setPhrase(new Phrase("Uploaded Files Information", ReportingUtil.headerFont)); + table.addCell(header); + + table.addCell(createCell("Record Type", true)); + table.addCell(createCell("Probe Images", true)); + table.addCell(createCell("Candidate Images", true)); + // Add Identifier Row + buildTableRow(table, "Subject Identifier", probe.getSubjectIdentifier(), candidate.getSubjectIdentifier()); + + buildImageSummaryRows(table, probe.getImageList(), candidate.getImageList()); + return table; + } + + private void buildImageSummaryRows(PdfPTable table, List probeImageList, List candidateImageList) { + Map probeImageMap = this.countImageTypes(probeImageList); + Map candidateImageMap = this.countImageTypes(candidateImageList); + + Set recordTypeSet= new TreeSet<>(); + + recordTypeSet.addAll(probeImageMap.keySet()); + recordTypeSet.addAll(candidateImageMap.keySet()); + + + for(Integer recordType : recordTypeSet){ + table.addCell(this.createCell(String.format("Type %d", recordType), true)); + + if(probeImageMap.containsKey(recordType)) { + table.addCell(this.createCell(String.valueOf(probeImageMap.get(recordType)), false)); + } else { + table.addCell(this.createCell("0", false)); + } + + if(candidateImageMap.containsKey(recordType)) { + table.addCell(this.createCell(String.valueOf(candidateImageMap.get(recordType)), false)); + } else { + table.addCell(this.createCell("0", false)); + } + } + + } + + private Map countImageTypes(List imageDataList){ + Map returnMap = new HashMap<>(); + for(ImageData imageData : imageDataList){ + returnMap.merge(imageData.getRecordType(), 1, Integer::sum); + } + + return returnMap; + } + + private void buildTableRow(PdfPTable table, String key, String probeValue, String candidateValue){ + if(StringUtils.isBlank(probeValue) || + StringUtils.isBlank(candidateValue)) + return; + + table.addCell(this.createCell(key, true)); + table.addCell(this.createCell(probeValue, false)); + table.addCell(this.createCell(candidateValue, false)); + + } + + private PdfPCell createCell(String phrase, boolean useBoldFont) { + PdfPCell cell = new PdfPCell(); + cell.setPadding(ReportingUtil.cellPadding); + cell.setBorderWidth(ReportingUtil.cellBorder); + if(useBoldFont) { + cell.setPhrase(new Phrase(phrase, this.boldFont)); + } else { + cell.setPhrase(new Phrase(phrase)); + } + + return cell; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/reporting/ReportingUtil.java b/web/src/main/java/org/mitre/iwp/web/reporting/ReportingUtil.java new file mode 100644 index 0000000..2dbe12f --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/reporting/ReportingUtil.java @@ -0,0 +1,713 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.reporting; + +import com.itextpdf.awt.PdfGraphics2D; +import com.itextpdf.text.Font; +import com.itextpdf.text.Image; +import com.itextpdf.text.List; +import com.itextpdf.text.*; +import com.itextpdf.text.pdf.*; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateFormatUtils; +import org.jfree.chart.ChartFactory; +import org.jfree.chart.JFreeChart; +import org.jfree.data.general.DefaultPieDataset; +import org.mitre.iwp.web.model.Annotation; +import org.mitre.iwp.web.model.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; + +public class ReportingUtil { + private static final Logger log = LoggerFactory.getLogger(ReportingUtil.class); + + protected static int tableSpacing = 30; + protected static float cellPadding = 5; + protected static float cellBorder = 2; + static Font boldFont = FontFactory.getFont(FontFactory.HELVETICA, 12, Font.BOLD); + static Font regularFont = FontFactory.getFont(FontFactory.HELVETICA, 12); + static int annotationCount = 1; + + protected static Font headerFont = FontFactory.getFont(FontFactory.HELVETICA, 16, Font.BOLDITALIC, + BaseColor.WHITE); + protected static Font subheaderFont = FontFactory.getFont(FontFactory.HELVETICA, 14, Font.BOLDITALIC, + BaseColor.BLACK); + + protected static Font pageHeaderFont = FontFactory.getFont(FontFactory.HELVETICA, 16, Font.BOLDITALIC); + + protected static Font listHeaderFont = new Font(Font.FontFamily.HELVETICA, 14, Font.BOLD); + protected static Font listSubitemFont = new Font(Font.FontFamily.HELVETICA, 12); + + public static byte[] createCaseOverview(ReviewCase cas) + throws DocumentException { + boldFont = FontFactory.getFont(FontFactory.HELVETICA, 12, Font.BOLD); + boldFont.setColor(BaseColor.BLACK); + + Document document = new Document(PageSize.A4, 25, 25, 25, 25); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PdfWriter writer = PdfWriter.getInstance(document, out); + + document.open(); + + Map statusCounts = new HashMap<>(); + + // Add Case Info Table + PdfPTable table = new PdfPTable(2); + table.setSpacingBefore(tableSpacing); + table.setSpacingAfter(tableSpacing); + table.setWidthPercentage(100); + + table.addCell(buildHeaderCell(2, "Title")); + table.addCell(buildIdentCell(2, cas.getTitle())); + table.addCell(buildHeaderCell(2, "Description")); + table.addCell(buildIdentCell(2, cas.getDescription())); + if(cas.getCreatedDate() != null) { + table.addCell(buildHeaderCell(2, "Created Date")); + table.addCell(buildIdentCell(2, cas.getCreatedDate().toString())); + } + table.addCell(buildHeaderCell(2, "ID")); + table.addCell(buildIdentCell(2, String.valueOf(cas.getId()))); + table.addCell(buildHeaderCell(2, "Priority")); + table.addCell(buildIdentCell(2, cas.getPriority())); + table.addCell(buildHeaderCell(2, "Created By")); + table.addCell(buildIdentCell(2, cas.getCreatedBy())); + if(cas.getDueDate() != null) { + table.addCell(buildHeaderCell(2, "Due Date")); + table.addCell(buildIdentCell(2, cas.getDueDate().toString())); + } + table.addCell(buildHeaderCell(2, "Searches and Reviews")); + table.addCell(buildIdentCell(2, "")); + table.addCell(buildHeaderCell(2, "Total number of searches/reviews")); + statusCounts.forEach((key, val) -> { + table.addCell(buildHeaderCell(2, key)); + table.addCell(buildIdentCell(2, val + "")); + }); + + document.add(table); + document.newPage(); + + // generate pie chart + ReportingUtil.createCaseReportPieChart(statusCounts, writer); + + Paragraph paragraph = ReportingUtil.getNewDocumentParagraph(new Phrase("# Requests:")); + document.add(paragraph); + document.close(); + + return out.toByteArray(); + } + + public static void createCaseReportPieChart(Map statusCounts, PdfWriter writer) { + /* Create Pie Chart */ + DefaultPieDataset data = new DefaultPieDataset<>(); + statusCounts.forEach(data::setValue); + JFreeChart chart = ChartFactory.createPieChart("Request Status", data, true, true, false); + + /* We have to insert this colored Pie Chart into the PDF file using iText now */ + PdfContentByte cb = writer.getDirectContent(); + float width = PageSize.A4.getWidth(); + float height = PageSize.A4.getHeight() / 2; + // Pie chart + PdfTemplate pie = cb.createTemplate(width, height); + Graphics2D g2d1 = new PdfGraphics2D(pie, width, height); + Rectangle2D r2d1 = new Rectangle2D.Double(0, 0, width, height); + chart.draw(g2d1, r2d1); + g2d1.dispose(); + cb.addTemplate(pie, 0, height); + } + + protected static PdfPTable createTableWithHeader(java.util.List headerItems) { + PdfPTable table = new PdfPTable(headerItems.size()); + table.setWidthPercentage(100); + table.setSpacingBefore(tableSpacing); + table.setSpacingAfter(tableSpacing); + Font fontH1 = FontFactory.getFont(FontFactory.HELVETICA, 16, Font.BOLDITALIC); + fontH1.setColor(BaseColor.WHITE); + for (String headerString : headerItems) { + PdfPCell header = new PdfPCell(); + header.setBorderWidth(ReportingUtil.cellBorder); + header.setPadding(cellPadding); + header.setBackgroundColor(BaseColor.GRAY); + header.setBorderWidth(ReportingUtil.cellBorder); + header.setPhrase(new Phrase(headerString, fontH1)); + table.addCell(header); + } + + return table; + } + + public static void handleCaseInformation(ReportData reportData, Document document) throws DocumentException { + if (null != reportData.getCaseInformation()) { + java.util.List subjectIdentifierList = new ArrayList<>(); + + if (null != reportData.getProbe().getSubjectIdentifier()) { + subjectIdentifierList.add(reportData.getProbe().getSubjectIdentifier()); + } + + for (Candidate candidate : reportData.getCandidates()) { + if (null != candidate.getSubjectIdentifier()) { + subjectIdentifierList.add(candidate.getSubjectIdentifier()); + } + } + + document.add(buildCaseInformationTable(reportData, subjectIdentifierList)); + + document.newPage(); + } + } + + protected static PdfPTable buildCaseInformationTable(ReportData reportData, + java.util.List subjectIdentifiers) { + PdfPTable table = new PdfPTable(2); + table.setSpacingBefore(tableSpacing); + table.setSpacingAfter(tableSpacing); + table.setWidthPercentage(100); + + CaseInformation caseInformation = reportData.getCaseInformation(); + + // Add Comparison Header/Date Information + table.addCell(buildHeaderCell(2, "Iris Image Comparisons")); + table.addCell(buildIdentCell(2, DateFormatUtils.format(new Date(caseInformation.getComparisonDate()), "yyyy-MM-dd HH:mm:SS"))); + + handlePersonInformation(table, reportData); + + table.addCell(buildHeaderCell(2, "Received Date")); + table.addCell(buildIdentCell(2, DateFormatUtils.format(new Date(caseInformation.getReceivedDate()), "yyyy-MM-dd HH:mm:SS"))); + + if (!subjectIdentifiers.isEmpty()) { + table.addCell(buildHeaderCell(2, "Referenced Subject(s)")); + table.addCell(buildIdentCell(2, String.join("\n", subjectIdentifiers))); + } + + return table; + } + + private static void handlePersonInformation(PdfPTable table, ReportData reportData) { + java.util.List conductedByList = getPersonList(reportData.getWorkedBy()); + java.util.List requestedByList = getPersonList(reportData.getCaseInformation().getRequestedBy()); + + if (!conductedByList.isEmpty() && !requestedByList.isEmpty()) { + table.addCell(buildHeaderCell(1, "Conducted By")); + table.addCell(buildHeaderCell(1, "Requested By")); + table.addCell(buildIdentCell(1, String.join("\n", conductedByList))); + table.addCell(buildIdentCell(1, String.join("\n", requestedByList))); + } else if (!conductedByList.isEmpty()) { + table.addCell(buildHeaderCell(2, "Conducted By")); + table.addCell(buildIdentCell(2, String.join("\n", conductedByList))); + } else if (!requestedByList.isEmpty()) { + table.addCell(buildHeaderCell(2, "Requested By")); + table.addCell(buildIdentCell(2, String.join("\n", requestedByList))); + } + } + + private static java.util.List getPersonList(Person person) { + java.util.List elementList = new ArrayList<>(); + + if (null == person) + return elementList; + + if (StringUtils.isNotBlank(person.getName())) { + elementList.add(person.getName()); + } + + if (StringUtils.isNotBlank(person.getTitle())) { + elementList.add(person.getTitle()); + } + + if (StringUtils.isNotBlank(person.getDepartment())) { + elementList.add(person.getDepartment()); + } + + if (StringUtils.isNotBlank(person.getAddress())) { + elementList.add(person.getAddress()); + } + + if (StringUtils.isNotBlank(person.getEmail())) { + elementList.add(person.getEmail()); + } + + if (StringUtils.isNotBlank(person.getPhone())) { + elementList.add(person.getPhone()); + } + + return elementList; + } + + private static java.util.List getPersonList(UserAuthentication ua) { + java.util.List elementList = new ArrayList<>(); + + Profile person = ua.getProfile(); + + if (null == person) + return elementList; + + if (StringUtils.isNotBlank(person.getFullName())) { + elementList.add(person.getFullName()); + } + + if (StringUtils.isNotBlank(person.getTitle())) { + elementList.add(person.getTitle()); + } + + if (StringUtils.isNotBlank(person.getDepartment())) { + elementList.add(person.getDepartment()); + } + + if (StringUtils.isNotBlank(person.getAddress())) { + elementList.add(person.getAddress()); + } + + if (StringUtils.isNotBlank(person.getEmail())) { + elementList.add(person.getEmail()); + } + + if (StringUtils.isNotBlank(person.getPhoneNumber())) { + elementList.add(person.getPhoneNumber()); + } + + return elementList; + } + + public static PdfPCell buildHeaderCell(int colSpan, String phraseText) { + PdfPCell cell = new PdfPCell(); + cell.setBackgroundColor(BaseColor.GRAY); + cell.setPaddingTop(1); + cell.setPaddingBottom(7); + cell.setVerticalAlignment(Element.ALIGN_LEFT); + cell.setColspan(colSpan); + cell.setBorderWidth(ReportingUtil.cellBorder); + + cell.addElement(new Phrase(phraseText, ReportingUtil.subheaderFont)); + return cell; + } + + private static PdfPCell buildIdentCell(int colSpan, String cellText) { + PdfPCell cell = new PdfPCell(); + cell.setPaddingTop(1); + cell.setPaddingBottom(7); + cell.setVerticalAlignment(Element.ALIGN_LEFT); + cell.setColspan(colSpan); + cell.setBorderWidth(ReportingUtil.cellBorder); + + Paragraph paragraph = new Paragraph(); + paragraph.setIndentationLeft(25); + paragraph.add(new Phrase(cellText, regularFont)); + cell.addElement(paragraph); + + return cell; + } + + public static void addEyeComparison(PdfPTable table, ImageData probeImage, ImageData candidateImage, String note) + throws IOException, BadElementException { + com.itextpdf.text.List probeEyeAnnotation = new com.itextpdf.text.List(); + com.itextpdf.text.List candidateEyeAnnotation = new com.itextpdf.text.List(); + + buildAnnotationOutput("Probe Annotations", probeEyeAnnotation, probeImage.getAnnotations()); + buildAnnotationOutput("Candidate Annotations", candidateEyeAnnotation, candidateImage.getAnnotations()); + + PdfPCell comparisonLabelCell = new PdfPCell(); + comparisonLabelCell.setColspan(2); + comparisonLabelCell.setPadding(ReportingUtil.cellPadding); + comparisonLabelCell.setBackgroundColor(BaseColor.WHITE); + comparisonLabelCell.setBorderWidth(ReportingUtil.cellBorder); + + if (probeImage.getEyeLabel() == candidateImage.getEyeLabel()) { + comparisonLabelCell.setPhrase( + new Phrase(String.format("%s Eye Comparison", probeImage.getEyeLabel().toString()), boldFont)); + } else { + comparisonLabelCell.setPhrase(new Phrase(String.format("Probe %s Eye to Candidate %s Eye Comparison", + probeImage.getEyeLabel().toString(), candidateImage.getEyeLabel().toString()), boldFont)); + } + + table.addCell(comparisonLabelCell); + + if (note != null && !note.equalsIgnoreCase("")) { + PdfPCell noteCell = new PdfPCell(); + noteCell.setColspan(2); + noteCell.setPadding(ReportingUtil.cellPadding); + noteCell.setBackgroundColor(BaseColor.WHITE); + noteCell.setBorderWidth(ReportingUtil.cellBorder); + + noteCell.addElement(new Phrase("Examiner's Note:", boldFont)); + noteCell.addElement(new Phrase(note, regularFont)); + + table.addCell(noteCell); + } + + table.addCell(buildListCell(probeEyeAnnotation)); + table.addCell(buildListCell(candidateEyeAnnotation)); + + if (StringUtils.isNotBlank(probeImage.getImageData())) { + table.addCell(buildImageCell( + drawImageAnnotationLabels(probeImage.getImageData(), probeImage.getAnnotations()), false)); + } else { + if (null != probeImage.getImageBytes() && probeImage.getImageBytes().length > 0) { + table.addCell(buildImageCell( + drawImageAnnotationLabels(probeImage.getImageBytes(), probeImage.getAnnotations()), false)); + } else { + table.addCell(buildTextCell("Error Building Image Cell.")); + } + } + + if (StringUtils.isNotBlank(candidateImage.getImageData())) { + table.addCell(buildImageCell( + drawImageAnnotationLabels(candidateImage.getImageData(), candidateImage.getAnnotations()), false)); + } else { + if (null != candidateImage.getImageBytes() && candidateImage.getImageBytes().length > 0) { + table.addCell(buildImageCell( + drawImageAnnotationLabels(candidateImage.getImageBytes(), candidateImage.getAnnotations()), + false)); + } else { + table.addCell(buildTextCell("Error Building Image Cell.")); + } + } + + if (StringUtils.isNotBlank(probeImage.getUnrollImageData()) + && StringUtils.isNotBlank(candidateImage.getUnrollImageData())) { + table.addCell(buildImageCell( + drawUnrollImageAnnotationLabels(probeImage.getUnrollImageData(), probeImage.getAnnotations()), + true)); + table.addCell(buildImageCell(drawUnrollImageAnnotationLabels(candidateImage.getUnrollImageData(), + candidateImage.getAnnotations()), true)); + } else if (probeImage.getUnrollImageBytes().length > 0 && candidateImage.getUnrollImageBytes().length > 0) { + table.addCell(buildImageCell( + drawUnrollImageAnnotationLabels(probeImage.getUnrollImageBytes(), probeImage.getAnnotations()), + true)); + table.addCell(buildImageCell(drawUnrollImageAnnotationLabels(candidateImage.getUnrollImageBytes(), + candidateImage.getAnnotations()), true)); + } + + probeImage.setReported(true); + candidateImage.setReported(true); + } + + protected static void buildAnnotationOutput(String annotationSeparator, List annotationList, + java.util.List annotations) { + if (null == annotations || annotations.isEmpty()) { + return; + } + + ListItem headerItem = new ListItem(annotationSeparator, listHeaderFont); + headerItem.setAlignment(Element.ALIGN_JUSTIFIED); + annotationList.setListSymbol(""); + annotationList.add(headerItem); + annotationCount = 1; + annotationList.add(walkAnnotations(annotations, 1)); + } + + private static List walkAnnotations(java.util.List annotations, int level) { + float indent = 10f * level; + List levelList = new List(indent); + levelList.setListSymbol(""); + for (org.mitre.iwp.web.model.Annotation ann : annotations) { + ListItem li; + if(!ann.getTempChildren().isEmpty()){ + li = new ListItem(ann.getText(), listHeaderFont); + li.setIndentationLeft(indent); + levelList.add(li); + + levelList.add(walkAnnotations(ann.getTempChildren(), level + 1)); + } else { + li = new ListItem(getAnnotationText(ann), listSubitemFont); + li.setIndentationLeft(indent); + levelList.add(li); + annotationCount++; + } + } + + return levelList; + } + + protected static String getAnnotationText(Annotation ann) { + if (ann.getValue() == null || ann.getValue().getData() == null) { + return ann.getText(); + } + + if (ann.getValue().getData().containsKey("adjustment")) { + return String.format("%s (%s)", ann.getText(), ann.getValue().getData().get("adjustment")); + } else if (ann.getValue().getData().containsKey("clipFactor")) { // Adaptive Contrast + return String.format("%s (%s)", ann.getText(), ann.getValue().getData().get("clipFactor")); + } + + return String.format("%d. %s", annotationCount, ann.getText()); + } + + protected static PdfPCell buildTextCell(String cellString) { + PdfPCell cell = new PdfPCell(new Phrase(cellString)); + cell.setBorderWidth(ReportingUtil.cellBorder); + cell.setPadding(cellPadding); + cell.setVerticalAlignment(Element.ALIGN_MIDDLE); + return cell; + } + + protected static PdfPCell buildTextCell(Paragraph cellParagraph) { + PdfPCell cell = new PdfPCell(new Phrase(cellParagraph)); + cell.setPadding(cellPadding); + cell.setVerticalAlignment(Element.ALIGN_LEFT); + return cell; + } + + protected static PdfPCell buildListCell(List list) { + PdfPCell cell = new PdfPCell(); + cell.setBorderWidth(ReportingUtil.cellBorder); + cell.addElement(list); + cell.setPaddingBottom(20); + return cell; + } + + protected static PdfPCell buildImageCell(byte[] candidateImage, boolean shrinkToFit) + throws IOException, BadElementException { + PdfPCell cell = new PdfPCell(); + cell.setBorderWidth(ReportingUtil.cellBorder); + cell.setPadding(cellPadding); + Image img = Image.getInstance(candidateImage); + if (shrinkToFit) { + img.scaleToFit(150, 150); + } + + cell.addElement(img); + + return cell; + } + + protected static PdfPCell buildImageCell(String imageString, boolean shrinkToFit) + throws IOException, BadElementException { + byte[] imageBytes = Base64.getDecoder().decode(imageString); + return buildImageCell(imageBytes, shrinkToFit); + } + + protected static Paragraph getNewDocumentParagraph(Element e) { + Paragraph p = new Paragraph(); + p.setSpacingAfter(ReportingUtil.tableSpacing); + p.add(e); + return p; + } + + public static boolean isValid(ReportData reportData) { + if (reportData == null) { + log.info("Missing Report Data object"); + return false; + } + + // Check Probe Info + if (reportData.getProbe() == null) { + log.info("Missing Probe object"); + return false; + } else { + if (isMissingImageData(reportData.getProbe().getImageList())) { + log.info("Missing Probe Image Data object"); + return false; + } + } + + // Check Candidate Info + if (reportData.getCandidates() == null || reportData.getCandidates().isEmpty()) { + log.info("Missing Candidate object"); + return false; + } else { + for (Candidate cand : reportData.getCandidates()) { + if (isMissingImageData(cand.getImageList())) { + log.info("Missing Candidate Image Data object"); + return false; + } + } + } + + return true; + } + + private static boolean isMissingImageData(java.util.List imageList) { + for (ImageData imageData : imageList) { + if (imageData.getEbtsImageId() == null && StringUtils.isEmpty(imageData.getImageData())) { + return true; + } + } + + return false; + } + + private static Integer getNumber(Object obj) { + log.info("Received: {}", obj); + if (obj instanceof Integer) { + return (Integer) obj; + } else if (obj instanceof Double) { + return ((Double) obj).intValue(); + } else if (obj instanceof String) { + Double tempDouble = Double.parseDouble(obj.toString()); + return tempDouble.intValue(); + } + + return null; + } + + public static byte[] drawImageAnnotationLabels(byte[] img, java.util.List annotations) + throws IOException { + if (annotations == null) { + return img; + } + + BufferedImage image = ImageIO.read(new ByteArrayInputStream(img)); + Graphics2D graphics = image.createGraphics(); + graphics.setColor(Color.green); + + annotationCount = 1; + walkLabelAnnotations(annotations, graphics); + + + ByteArrayOutputStream ret = new ByteArrayOutputStream(); + ImageIO.write(image, "png", ret); + + return ret.toByteArray(); + } + + private static void walkLabelAnnotations(java.util.List annotations, Graphics2D graphics) { + for (Annotation annotation : annotations) { + processAnnotation(annotation, graphics); + } + } + + private static void processPoint(Annotation annotation, Graphics2D graphics) { + Integer x = getNumber(annotation.getValue().getPoints().get(0).getX()); + Integer y = getNumber(annotation.getValue().getPoints().get(0).getY()); + + if (x != null && y != null) { + y -= 10; + graphics.drawString(Integer.toString(annotationCount), x, y); + log.info("Added Point: {}, {}", x, y); + } else { + log.warn("Could not add Point"); + } + } + + private static void processCircle(Annotation annotation, Graphics2D graphics) { + Integer x = getNumber(annotation.getValue().getPoints().get(0).getX()); + Integer y = getNumber(annotation.getValue().getPoints().get(0).getY()); + + Integer radius = getNumber(annotation.getValue().getData().get("radius")); + + if (x != null && y != null && radius != null) { + y -= radius + 10; + graphics.drawString(Integer.toString(annotationCount), x, y); + log.info("Added Circle: {}, {}, {}", x, y, radius); + } else { + log.warn("Could not add Circle"); + } + } + + private static void processPolygonAndPencil(Annotation annotation, Graphics2D graphics) { + if (!annotation.getValue().getPoints().isEmpty()) { + Integer x = getNumber(annotation.getValue().getPoints().get(0).getX()); + Integer y = getNumber(annotation.getValue().getPoints().get(0).getY()); + + if (x != null && y != null) { + y -= 10; + graphics.drawString(Integer.toString(annotationCount), x, y); + log.info("Added {}: {}, {}", annotation.getText(), x, y); + } else { + log.warn("Could not add {}", annotation.getText()); + } + } + } + + private static void handlePermanentAnnotations(Annotation annotation, Graphics2D graphics) { + // pulled from annotation.ts from the AnnotationType enum + switch (annotation.getType()) { + case "0": // point + processPoint(annotation, graphics); + break; + case "1": // circle + processCircle(annotation, graphics); + break; + case "2", "3": // polygon, pencil + processPolygonAndPencil(annotation, graphics); + break; + default: + log.info("The annotation type is not a point, circle, polygon, or pencil."); + }// end of switch + } + + private static void processAnnotation(Annotation annotation, Graphics2D graphics) { + if (!annotation.getTempChildren().isEmpty()) { + // Handle temporary children logic here + walkLabelAnnotations(annotation.getTempChildren(), graphics); + } else { + // Process the rest of the annotation logic here + handlePermanentAnnotations(annotation, graphics); + + if (annotation.getValue() == null || annotation.getValue().getData() == null) + return; + + annotation.getValue().getData().put("annotationNumber", String.valueOf(annotationCount)); + annotationCount++; + } + } + + public static byte[] drawImageAnnotationLabels(String img, java.util.List annotations) + throws IOException { + byte[] imageBytes = Base64.getDecoder().decode(img); + return drawImageAnnotationLabels(imageBytes, annotations); + } + + private static byte[] drawUnrollImageAnnotationLabels(byte[] img, java.util.List annotations) + throws IOException { + BufferedImage image = ImageIO.read(new ByteArrayInputStream(img)); + Graphics2D graphics = image.createGraphics(); + graphics.setColor(Color.green); + + for (Annotation annotation : annotations) { + // pulled from annotation.ts from the AnnotationType enum + if (annotation.getType().equals("0")) { + Integer x = getNumber(annotation.getValue().getData().get("unrollX")); + Integer y = getNumber(annotation.getValue().getData().get("unrollY")); + Integer tempLabel = getNumber(annotation.getValue().getData().get("annotationNumber")); + String label = tempLabel == null ? "" : tempLabel.toString(); + + if (x != null && y != null) { + y -= 10; + graphics.drawString(label, x, y); + log.info("Added annotation: {}, {}", x, y); + } else { + log.warn("Could not add annotation"); + } + } + } + + ByteArrayOutputStream ret = new ByteArrayOutputStream(); + ImageIO.write(image, "png", ret); + + return ret.toByteArray(); + } + + private static byte[] drawUnrollImageAnnotationLabels(String img, java.util.List annotations) + throws IOException { + byte[] imageBytes = Base64.getDecoder().decode(img); + return drawUnrollImageAnnotationLabels(imageBytes, annotations); + + } + + public static boolean checkAdjudication(Map adjudicationResults, String candidateAdjudication) { + if(adjudicationResults.containsKey(candidateAdjudication)){ + return adjudicationResults.get(candidateAdjudication).equalsIgnoreCase("TRUE"); + } else { + log.error("Unknown adjudication value {}. Omitting candidate", candidateAdjudication); + } + + return false; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/reporting/SrbReporting.java b/web/src/main/java/org/mitre/iwp/web/reporting/SrbReporting.java new file mode 100644 index 0000000..3d0c418 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/reporting/SrbReporting.java @@ -0,0 +1,270 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.reporting; + +import com.itextpdf.text.*; +import com.itextpdf.text.pdf.PdfPCell; +import com.itextpdf.text.pdf.PdfPTable; +import com.itextpdf.text.pdf.PdfWriter; +import org.apache.commons.lang3.StringUtils; +import org.mitre.iwp.web.IwpWebProperties; +import org.mitre.iwp.web.data.EbtsImage; +import org.mitre.iwp.web.exception.InvalidMessageException; +import org.mitre.iwp.web.model.*; +import org.mitre.iwp.web.service.EbtsImageRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Arrays; + +@Service +public class SrbReporting { + private static final Logger log = LoggerFactory.getLogger(SrbReporting.class); + + private final EbtsImageRepository imageRepository; + + private final IwpWebProperties iwpWebProperties; + + private static final String notFoundMessage = "Image data not found"; + + private Font boldFont; + + public SrbReporting(EbtsImageRepository imageRepository, + IwpWebProperties iwpWebProperties) { + this.imageRepository = imageRepository; + this.iwpWebProperties = iwpWebProperties; + } + + + public byte[] createPdfReport(ReportData reportData) throws DocumentException, InvalidMessageException, IOException { + boldFont = FontFactory.getFont(FontFactory.HELVETICA, 12, Font.BOLD); + boldFont.setColor(BaseColor.BLACK); + + Probe probe = reportData.getProbe(); + + if(null == probe.getImageList().get(0)){ + log.error("probe image list is empty"); + return new byte[0]; + } + + ImageData probeImage = probe.getImageList().get(0); + + Document document = new Document(PageSize.A4, 25, 25, 25, 25); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PdfWriter.getInstance(document, out); + + document.open(); + + ReportingUtil.handleCaseInformation(reportData, document); + + // Add Probe Table + PdfPTable probeTable = buildProbeTable(probe); + Paragraph paragraph = ReportingUtil.getNewDocumentParagraph(probeTable); + document.add(paragraph); + document.newPage(); + + PdfPTable candidateTable = ReportingUtil.createTableWithHeader(Arrays.asList("Candidate UCN", "Original Image", "Notes")); + + for(Candidate candidate : reportData.getCandidates()) { + if(ReportingUtil.checkAdjudication(this.iwpWebProperties.getAdjudicationValue(), candidate.getAdjudicationResults())) { + addCandidateRow(candidateTable, candidate); + for(ImageData candidateImage : candidate.getImageList()){ + PdfPTable comparisonTable = new PdfPTable(2); + comparisonTable.setWidthPercentage(100); + comparisonTable.setSpacingBefore(ReportingUtil.tableSpacing); + comparisonTable.setSpacingAfter(ReportingUtil.tableSpacing); + + PdfPCell header = new PdfPCell(); + String probeIdentifier = probe.getSubjectIdentifier(); + String candidateIdentifier = candidate.getSubjectIdentifier(); + header.setColspan(2); + header.setPadding(ReportingUtil.cellPadding); + header.setBackgroundColor(BaseColor.BLACK); + header.setBorderWidth(ReportingUtil.cellBorder); + header.setPhrase(new Phrase(String.format("Comparison of %s and %s", (probeIdentifier == null || probeIdentifier.trim().isEmpty()) ? "PROBE" : probeIdentifier , (candidateIdentifier == null || candidateIdentifier.trim().isEmpty()) ? "CANDIDATE" : candidateIdentifier), ReportingUtil.headerFont)); + comparisonTable.addCell(header); + + PdfPCell resultCell = new PdfPCell(); + resultCell.setColspan(2); + resultCell.setPadding(ReportingUtil.cellPadding); + resultCell.setBackgroundColor(BaseColor.GRAY); + resultCell.setBorderWidth(ReportingUtil.cellBorder); + String phrase = ""; + if(candidate.getAdjudicationResults() != null){ + phrase=candidate.getAdjudicationResults(); + } + resultCell.setPhrase(new Phrase(phrase, ReportingUtil.subheaderFont)); + comparisonTable.addCell(resultCell); + + // Build Eye Comparison + if(probeImage.getEbtsImageId() != null) { + EbtsImage ebtsImage; + log.info("EbtsImageId: {}", probeImage.getEbtsImageId()); + log.info("RasterizedEbtsImageId: {}", probeImage.getRasterizeEbtsImageId()); + + if( probeImage.getRasterizeEbtsImageId() == null) { + ebtsImage = imageRepository.findById(probeImage.getEbtsImageId()).orElseThrow(()->new InvalidMessageException(notFoundMessage)); + } else { + ebtsImage = imageRepository.findById(probeImage.getRasterizeEbtsImageId()).orElseThrow(()->new InvalidMessageException(notFoundMessage)); + } + + if(null != ebtsImage) { + try { + probeImage.setImageBytes(EbtsImage.getImageBytesPng(ebtsImage)); + }catch(FileNotFoundException fnfe){ + log.error("File not found {}",ebtsImage); + } + } else { + break; + } + } + + if(candidateImage.getEbtsImageId() != null) { + EbtsImage ebtsImage; + if( candidateImage.getRasterizeEbtsImageId() == null) { + ebtsImage = imageRepository.findById(candidateImage.getEbtsImageId()).orElseThrow(()->new InvalidMessageException(notFoundMessage)); + } else { + ebtsImage = imageRepository.findById(candidateImage.getRasterizeEbtsImageId()).orElseThrow(()->new InvalidMessageException(notFoundMessage)); + } + + if(null != ebtsImage) { + try { + candidateImage.setImageBytes(EbtsImage.getImageBytesPng(ebtsImage)); + }catch(FileNotFoundException fnfe){ + log.error("File not found {}",ebtsImage); + } + } else { + break; + } + } + + //get the unroll images + if( probeImage.getUnrollImageId() != null ) { + EbtsImage ebtsImage; + ebtsImage = imageRepository.findById(probeImage.getUnrollImageId()).orElseThrow(()->new InvalidMessageException(notFoundMessage)); + if( ebtsImage != null ) probeImage.setUnrollImageBytes(EbtsImage.getImageBytesPng(ebtsImage)); + } + + if( candidateImage.getUnrollImageId() != null ) { + EbtsImage ebtsImage; + ebtsImage = imageRepository.findById(candidateImage.getUnrollImageId()).orElseThrow(()->new InvalidMessageException(notFoundMessage)); + if( ebtsImage != null ) candidateImage.setUnrollImageBytes(EbtsImage.getImageBytesPng(ebtsImage)); + } + + + ReportingUtil.addEyeComparison(comparisonTable, probeImage, candidateImage, candidate.getNote()); + document.add(ReportingUtil.getNewDocumentParagraph(comparisonTable)); + document.newPage(); + } + } + } + + document.newPage(); + paragraph = ReportingUtil.getNewDocumentParagraph(new Chunk("Appendix - Candidate Images", ReportingUtil.pageHeaderFont)); + document.add(paragraph); + paragraph = ReportingUtil.getNewDocumentParagraph(candidateTable); + document.add(paragraph); + document.close(); + + return out.toByteArray(); + } + + private PdfPTable buildProbeTable(Probe probe) throws BadElementException, InvalidMessageException, IOException { + PdfPTable table = new PdfPTable(2); + table.setWidthPercentage(100); + table.setSpacingBefore(ReportingUtil.tableSpacing); + table.setSpacingAfter(ReportingUtil.tableSpacing); + + PdfPCell header = new PdfPCell(); + header.setColspan(2); + header.setPadding(ReportingUtil.cellPadding); + header.setBackgroundColor(BaseColor.BLACK); + header.setBorderWidth(ReportingUtil.cellBorder); + header.setPhrase(new Phrase("Probe Information", ReportingUtil.headerFont)); + table.addCell(header); + + // Set Subject Identifier Information + PdfPCell resultCell = new PdfPCell(); + resultCell.setColspan(2); + resultCell.setPadding(ReportingUtil.cellPadding); + resultCell.setBackgroundColor(BaseColor.GRAY); + resultCell.setBorderWidth(ReportingUtil.cellBorder); + + if(StringUtils.isNotBlank(probe.getSubjectIdentifier())) { + resultCell.setPhrase(new Phrase(probe.getSubjectIdentifier(), ReportingUtil.subheaderFont)); + } else { + resultCell.setPhrase(new Phrase("Missing Subject Identifier", ReportingUtil.subheaderFont)); + } + + table.addCell(resultCell); + + // Set Probe Image + PdfPCell keyCell = new PdfPCell(); + keyCell.setPadding(ReportingUtil.cellPadding); + keyCell.setBorderWidth(ReportingUtil.cellBorder); + keyCell.setPhrase(new Phrase("Original Image", boldFont)); + table.addCell(keyCell); + table.addCell(buildImageCellFromId(probe.getImageList().get(0).getEbtsImageId(), false)); + + buildTableRow(table, "Name", probe.getName()); + buildTableRow(table, "Transaction Control Number", probe.getTransactionControlNumber()); + buildTableRow(table, "Transaction Control Reference", probe.getTransactionControlReference()); + buildTableRow(table, "Image Notes", probe.getImageList().get(0).getImageNotes()); + + return table; + } + + private void buildTableRow(PdfPTable table, String key, String value) { + if(StringUtils.isBlank(value)) + return; + + PdfPCell keyCell = new PdfPCell(); + keyCell.setPadding(ReportingUtil.cellPadding); + keyCell.setBorderWidth(ReportingUtil.cellBorder); + keyCell.setPhrase(new Phrase(key, boldFont)); + + PdfPCell valueCell = new PdfPCell(); + valueCell.setPadding(ReportingUtil.cellPadding); + valueCell.setBorderWidth(ReportingUtil.cellBorder); + valueCell.setPhrase(new Phrase(value)); + + table.addCell(keyCell); + table.addCell(valueCell); + } + + private void addCandidateRow(PdfPTable table, Candidate candidate) throws BadElementException, InvalidMessageException, IOException{ + for(ImageData image : candidate.getImageList()) { + if(image.getEbtsImageId() != null) { + table.addCell(ReportingUtil.buildTextCell((candidate.getSubjectIdentifier()))); + table.addCell(buildImageCellFromId(image.getEbtsImageId(), false)); + table.addCell(ReportingUtil.buildTextCell(image.getImageNotes())); + } + } + } + + private PdfPCell buildImageCellFromId(long imageId, boolean shrinkToFit) throws InvalidMessageException, BadElementException, IOException { + EbtsImage ebtsImage = imageRepository.findById(imageId).orElseThrow(()->new InvalidMessageException(notFoundMessage)); + try { + return ReportingUtil.buildImageCell(EbtsImage.getImageBytesPng(ebtsImage), shrinkToFit); + }catch(FileNotFoundException fne){ + log.error("File not found for image:id {}",imageId); + PdfPCell ce = new PdfPCell(); + ce.setPadding(ReportingUtil.cellPadding); + ce.setBorderWidth(ReportingUtil.cellBorder); + ce.setPhrase(new Phrase("Image not available", boldFont)); + return ce; + } + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/reporting/XmlReporting.java b/web/src/main/java/org/mitre/iwp/web/reporting/XmlReporting.java new file mode 100644 index 0000000..44cdcba --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/reporting/XmlReporting.java @@ -0,0 +1,270 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.reporting; + +import com.itextpdf.text.*; +import com.itextpdf.text.pdf.PdfPCell; +import com.itextpdf.text.pdf.PdfPTable; +import com.itextpdf.text.pdf.PdfWriter; +import org.mitre.iwp.web.IwpWebProperties; +import org.mitre.iwp.web.data.EbtsImage; +import org.mitre.iwp.web.exception.InvalidMessageException; +import org.mitre.iwp.web.model.*; +import org.mitre.iwp.web.service.EbtsImageRepository; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +@Service +public class XmlReporting { + private final EbtsImageRepository imageRepository; + + private final IwpWebProperties iwpWebProperties; + + private static final String notFoundMessage = "Image data not found"; + + public XmlReporting(EbtsImageRepository imageRepository, + IwpWebProperties iwpWebProperties) { + this.imageRepository = imageRepository; + this.iwpWebProperties = iwpWebProperties; + } + + public byte[] createPdfReport(ReportData reportData) throws DocumentException, InvalidMessageException, IOException { + if(ReportingUtil.isValid(reportData)) + { + // Create Pdf Document elements + Document document = new Document(PageSize.A4, 25, 25, 25, 25); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PdfWriter.getInstance(document, out); + + document.open(); + + // Parse Case Information (if applicable) + ReportingUtil.handleCaseInformation(reportData, document); + + // Parse Data + parseReportData(reportData, document); + + document.close(); + + return out.toByteArray(); + } + + return new byte[0]; + } + + private void parseReportData(ReportData reportData, Document document) throws InvalidMessageException, IOException, DocumentException { + Probe probe = reportData.getProbe(); + + // Loop through candidates/images and compare to probe + for (Candidate candidate : reportData.getCandidates()) { + if(ReportingUtil.checkAdjudication(this.iwpWebProperties.getAdjudicationValue(), candidate.getAdjudicationResults())) { + for (ImageData candidateImage : candidate.getImageList()) { + for (ImageData probeImage : probe.getImageList()) { + if (candidateImage.getEyeLabel() == probeImage.getEyeLabel()) { + PdfPTable comparisonTable = new PdfPTable(2); + comparisonTable.setWidthPercentage(100); + comparisonTable.setSpacingBefore(ReportingUtil.tableSpacing); + comparisonTable.setSpacingAfter(ReportingUtil.tableSpacing); + comparisonTable.setSpacingBefore(ReportingUtil.tableSpacing); + comparisonTable.setSpacingAfter(ReportingUtil.tableSpacing); + + PdfPCell header = new PdfPCell(); + String probeIdentifier = probe.getSubjectIdentifier(); + String candidateIdentifier = candidate.getSubjectIdentifier(); + header.setColspan(2); + header.setPadding(ReportingUtil.cellPadding); + header.setBackgroundColor(BaseColor.BLACK); + header.setBorderWidth(ReportingUtil.cellBorder); + header.setPhrase(new Phrase(String.format("Comparison of %s and %s", (probeIdentifier == null || probeIdentifier.trim().isEmpty()) ? "PROBE" : probeIdentifier , (candidateIdentifier == null || candidateIdentifier.trim().isEmpty()) ? "CANDIDATE" : candidateIdentifier), ReportingUtil.headerFont)); + comparisonTable.addCell(header); + + PdfPCell resultCell = new PdfPCell(); + resultCell.setColspan(2); + resultCell.setPadding(ReportingUtil.cellPadding); + resultCell.setPadding(ReportingUtil.cellPadding); + resultCell.setBackgroundColor(BaseColor.GRAY); + resultCell.setBorderWidth(ReportingUtil.cellBorder); + resultCell.setPhrase(new Phrase(candidate.getAdjudicationResults(), ReportingUtil.subheaderFont)); + comparisonTable.addCell(resultCell); + + // Build Eye Comparison + if (probeImage.getEbtsImageId() != null) { + EbtsImage ebtsImage; + if (probeImage.getRasterizeEbtsImageId() == null) { + ebtsImage = imageRepository.findById(probeImage.getEbtsImageId()).orElseThrow(() -> new InvalidMessageException(notFoundMessage)); + } else { + ebtsImage = imageRepository.findById(probeImage.getRasterizeEbtsImageId()).orElseThrow(() -> new InvalidMessageException(notFoundMessage)); + } + + if (null != ebtsImage) { + probeImage.setImageBytes(EbtsImage.getImageBytesPng(ebtsImage)); + } else { + break; + } + } + + if (candidateImage.getEbtsImageId() != null) { + EbtsImage ebtsImage; + if (candidateImage.getRasterizeEbtsImageId() == null) { + ebtsImage = imageRepository.findById(candidateImage.getEbtsImageId()).orElseThrow(() -> new InvalidMessageException(notFoundMessage)); + } else { + ebtsImage = imageRepository.findById(candidateImage.getRasterizeEbtsImageId()).orElseThrow(() -> new InvalidMessageException(notFoundMessage)); + } + + if (null != ebtsImage) { + candidateImage.setImageBytes(EbtsImage.getImageBytesPng(ebtsImage)); + } else { + break; + } + } + + + //get the unroll images + if (probeImage.getUnrollImageId() != null) { + EbtsImage ebtsImage; + ebtsImage = imageRepository.findById(probeImage.getUnrollImageId()).orElseThrow(() -> new InvalidMessageException(notFoundMessage)); + if (ebtsImage != null) + probeImage.setUnrollImageBytes(EbtsImage.getImageBytesPng(ebtsImage)); + } + + if (candidateImage.getUnrollImageId() != null) { + EbtsImage ebtsImage; + ebtsImage = imageRepository.findById(candidateImage.getUnrollImageId()).orElseThrow(() -> new InvalidMessageException(notFoundMessage)); + if (ebtsImage != null) + candidateImage.setUnrollImageBytes(EbtsImage.getImageBytesPng(ebtsImage)); + } + + + ReportingUtil.addEyeComparison(comparisonTable, probeImage, candidateImage, candidate.getNote()); + document.add(ReportingUtil.getNewDocumentParagraph(comparisonTable)); + document.newPage(); + } + } + } + } + } + + Paragraph paragraph = ReportingUtil.getNewDocumentParagraph(new Chunk("Appendix - Unmatched Images", ReportingUtil.pageHeaderFont)); + boolean paragraphAdded = false; + + for (ImageData image : probe.getImageList()) { + if (!image.isReported()) { + if (!paragraphAdded) { + document.add(paragraph); + paragraphAdded = true; + } else { + document.newPage(); + } + document.add(addUnreportedTable(image, probe.getSubjectIdentifier())); + } + } + + for (Candidate candidate : reportData.getCandidates()) { + for (ImageData image : candidate.getImageList()) { + if (!image.isReported()) { + if (!paragraphAdded) { + document.add(paragraph); + paragraphAdded = true; + } else { + document.newPage(); + } + + document.add(addUnreportedTable(image, candidate.getSubjectIdentifier())); + } + } + } + + //add the appendix for the original images + document.newPage(); + Paragraph originalImagesAppendix = ReportingUtil.getNewDocumentParagraph(new Chunk("Appendix - Original Probe Images", ReportingUtil.pageHeaderFont)); + + document.add(originalImagesAppendix); + + PdfPTable probeTable = ReportingUtil.createTableWithHeader(Arrays.asList("Original Image", "Notes")); + + for( ImageData data : probe.getImageList() ) { + if(data.getEbtsImageId() != null) { + probeTable.addCell(buildImageCellFromId(data.getEbtsImageId(), false)); + probeTable.addCell(ReportingUtil.buildTextCell(data.getImageNotes())); + } + }//end of for + + Paragraph imageParagraph = ReportingUtil.getNewDocumentParagraph(probeTable); + document.add(imageParagraph); + + for( Candidate candidate : reportData.getCandidates() ) { + if(ReportingUtil.checkAdjudication(this.iwpWebProperties.getAdjudicationValue(), candidate.getAdjudicationResults())) { + document.newPage(); + + Paragraph candidateAppendix = ReportingUtil.getNewDocumentParagraph(new Chunk(String.format("Appendix - Candidate %s Original Images", candidate.getSubjectIdentifier()), ReportingUtil.pageHeaderFont)); + document.add(candidateAppendix); + + PdfPTable candidateTable = ReportingUtil.createTableWithHeader(Arrays.asList("Original Image", "Notes")); + + for (ImageData data : candidate.getImageList()) { + if (data.getEbtsImageId() != null) { + candidateTable.addCell(buildImageCellFromId(data.getEbtsImageId(), false)); + candidateTable.addCell(ReportingUtil.buildTextCell(data.getImageNotes())); + } + }//end of for + + Paragraph candidateImagesParagraph = ReportingUtil.getNewDocumentParagraph(candidateTable); + document.add(candidateImagesParagraph); + } + + }//end of for loop + + }//end of parse report + + + private PdfPCell buildImageCellFromId(long imageId, boolean shrinkToFit) throws BadElementException, IOException, InvalidMessageException { + EbtsImage ebtsImage = imageRepository.findById(imageId).orElseThrow(()->new InvalidMessageException(notFoundMessage)); + return ReportingUtil.buildImageCell(EbtsImage.getImageBytesPng(ebtsImage), shrinkToFit); + } + + private PdfPTable addUnreportedTable(ImageData image, String subjectIdentifier) throws InvalidMessageException, BadElementException, IOException { + PdfPTable table = new PdfPTable(1); + table.setWidthPercentage(50); + table.setSpacingBefore(ReportingUtil.tableSpacing); + table.setSpacingAfter(ReportingUtil.tableSpacing); + + PdfPCell header = new PdfPCell(); + header.setPadding(ReportingUtil.cellPadding); + header.setBackgroundColor(BaseColor.GRAY); + header.setBorderWidth(ReportingUtil.cellBorder); + header.setPhrase(new Phrase(String.format("%s - %s Eye", subjectIdentifier, image.getEyeLabel().toString()), ReportingUtil.subheaderFont)); + table.addCell(header); + + if(image.getEbtsImageId() == 0) { + com.itextpdf.text.List eyeAnnotation = new com.itextpdf.text.List(); + ReportingUtil.buildAnnotationOutput("Annotations", eyeAnnotation, image.getAnnotations()); + table.addCell(ReportingUtil.buildListCell(eyeAnnotation)); + + EbtsImage ebtsImage = imageRepository.findById(image.getEbtsImageId()).orElseThrow(()->new InvalidMessageException(notFoundMessage)); + table.addCell(ReportingUtil.buildImageCell(EbtsImage.getImageBytesPng(ebtsImage), false)); + table.addCell(new PdfPCell(new Phrase(""))); + + } else { + EbtsImage ebtsImage = imageRepository.findById(image.getEbtsImageId()).orElseThrow(()->new InvalidMessageException(notFoundMessage)); + + if(null == ebtsImage){ + table.addCell(ReportingUtil.buildTextCell("Error Building Image Cell.")); + } else { + table.addCell(ReportingUtil.buildImageCell(EbtsImage.getImageBytesPng(ebtsImage), false)); + } + } + + return table; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/rest/AuthController.java b/web/src/main/java/org/mitre/iwp/web/rest/AuthController.java new file mode 100644 index 0000000..d8f4426 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/rest/AuthController.java @@ -0,0 +1,486 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.rest; + +import java.security.Principal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.IterableUtils; +import org.mitre.iwp.web.dto.*; +import org.mitre.iwp.web.model.Privilege; +import org.mitre.iwp.web.model.UserAuthentication; +import org.mitre.iwp.web.model.UserRole; +import org.mitre.iwp.web.security.IwpAuthService; +import org.mitre.iwp.web.security.IwpHttpConfig; +import org.mitre.iwp.web.security.IwpUserDetailsService; +import org.mitre.iwp.web.service.NotificationService; +import org.mitre.iwp.web.service.PrivilegeRepository; +import org.mitre.iwp.web.service.UserAuthenticationRepository; +import org.mitre.iwp.web.service.UserRoleRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class AuthController { + private static final Logger logger = LoggerFactory.getLogger(AuthController.class); + + private final IwpUserDetailsService iwpUserDetailsService; + + private final NotificationService notificationService; + + private final UserAuthenticationRepository userAuthenticationRepository; + + private final UserRoleRepository roleRepository; + + private final PrivilegeRepository privilegeRepository; + + private final UserMapper userMapper; + + private IwpAuthService authService; + private IwpHttpConfig iwpHttpConfig; + private final PasswordEncoder passwordEncoder; + + private static final String ERROR_DELETE_MESSAGE = "Error deleting privilege"; + private static final String ERROR_UPDATE_MESSAGE = "Error updating password"; + private static final String ERROR_INCORRECT_PASSWORD_MESSAGE = "Incorrect Password for User"; + + public AuthController(IwpUserDetailsService iwpUserDetailsService, NotificationService notificationService, + UserAuthenticationRepository userAuthenticationRepository, UserRoleRepository roleRepository, + PrivilegeRepository privilegeRepository, UserMapper userMapper, IwpHttpConfig iwpHttpConfig, + IwpAuthService authService, PasswordEncoder passwordEncoder) { + this.iwpUserDetailsService = iwpUserDetailsService; + this.notificationService = notificationService; + this.userAuthenticationRepository = userAuthenticationRepository; + this.roleRepository = roleRepository; + this.privilegeRepository = privilegeRepository; + this.authService = authService; + this.userMapper = userMapper; + this.iwpHttpConfig = iwpHttpConfig; + this.passwordEncoder = passwordEncoder; + } + + @GetMapping(value = "/api/logon/info") + public Boolean getIsSecure() { + return iwpHttpConfig.getIsSecure(); + } + + @Secured({ "ROLE_USER" }) + @GetMapping(value = "/api/admin/user", produces = { MediaType.TEXT_PLAIN_VALUE }) + @ResponseBody + public String getUser(Principal principal) { + return authService.getCurrentUsername(principal); + } + + @Secured({ "ROLE_ADMIN" }) + @GetMapping(value = "/api/admin/users") + @ResponseBody + public Collection getAllUsers() { + List uaList = IterableUtils.toList(userAuthenticationRepository.findAll()); + + for (UserAuthentication ua : uaList) { + ua.setPassword(""); + } + + return uaList; + } + + @Secured({ "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/admin/userList") + @ResponseBody + public Collection getAllUsernames() { + List users = IterableUtils.toList(userAuthenticationRepository.findAll()); + return users.stream().map(UserAuthentication::getUsername).collect(Collectors.toList()); + } + + @Secured({ "ROLE_ADMIN" }) + @PostMapping(value = "/api/admin/user/add/update/roles") + public ResponseEntity addUser(@RequestBody UserAuthenticationDTO userObject) { + try { + if (userObject.getId() == -1 && userObject.getIsNewUser()) { + iwpUserDetailsService.addUser(userObject, userObject.getUsername()); + } else { + UserAuthentication existingUser = userAuthenticationRepository.findByUsername(userObject.getUsername()); + + existingUser.setRoles(userObject.getRoles()); + + userAuthenticationRepository.save(existingUser); + } + return ResponseEntity.ok().body("OK"); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error adding user"); + } + } + + @Secured({ "ROLE_ADMIN" }) + @PostMapping(value = "/api/admin/user/delete") + public ResponseEntity deleteUser(@RequestBody UserAuthenticationDTO userObject) { + try { + iwpUserDetailsService.deleteUser(userObject.getId()); + return ResponseEntity.ok().body("OK"); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error deleting user"); + } + } + + @Secured({"ROLE_USER"}) + @PostMapping(value = "/api/admin/check/password") + public ResponseEntity checkPassword(@RequestBody UserPasswordData userPasswordData) { + try { + UserDetails userDetails = iwpUserDetailsService.loadUserByUsername(userPasswordData.userName); + if (passwordEncoder.matches(userPasswordData.oldPassword, userDetails.getPassword())) { + logger.info("Passwords match. Calling updatePassword"); + return ResponseEntity.ok().body("OK"); + } else { + logger.info("PASSWORDS DID NOT MATCH."); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ERROR_INCORRECT_PASSWORD_MESSAGE); + } + } catch (Exception e) { + logger.error("ERROR OCCURED DURING PASSWORD CHANGE."); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ERROR_UPDATE_MESSAGE); + } + } + + @Secured({ "ROLE_USER" }) + @PostMapping(value = "/api/admin/user/password") + public ResponseEntity updateUserPassword(@RequestBody UserPasswordData userPasswordData) { + try { + String newPasswordEncrypt = passwordEncoder.encode(userPasswordData.newPassword); + // Don't need to check if the current password is the same because it gets checked on the frontend before this method is called. + iwpUserDetailsService.updatePassword(userPasswordData.userName, newPasswordEncrypt); + return ResponseEntity.ok().body("OK"); + } catch (Exception e) { + logger.error("ERROR OCCURED DURING PASSWORD CHANGE."); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ERROR_UPDATE_MESSAGE); + } + } + + public static class UserPasswordData { + String userName; + + public void setUserName(String userName) { + this.userName = userName; + } + + String oldPassword; + + public void setOldPassword(String oldPassword) { + this.oldPassword = oldPassword; + } + + String newPassword; + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } + } + + /*** + * + * @return list of UserNotifications for this user + */ + @Secured({ "ROLE_USER" }) + @GetMapping(value = "/api/admin/user/notifications") + public ResponseEntity getUserNotifications(Principal principal) { + UserAuthentication ua; + try { + ua = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + return new ResponseEntity<>(notificationService.getNotificationsForUser(ua.getId()), HttpStatus.OK); + } + + /*** + * @return count of UserNotifications for this user + */ + @Secured({ "ROLE_USER" }) + @GetMapping(value = "/api/admin/user/notifications/count") + public ResponseEntity getUserNotificationCount(Principal principal) { + UserAuthentication ua; + try { + ua = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + return new ResponseEntity<>(notificationService.getNotificationCountForUser(ua.getId()), HttpStatus.OK); + } + + + @Secured({ "ROLE_USER" }) + @GetMapping(value = "/api/admin/user/profile") + public ResponseEntity getProfile(Principal principal) { + UserAuthentication ua; + try { + ua = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + return new ResponseEntity<>(ua.getProfile(), HttpStatus.OK); + } + + // assumes that the profile is already there since it is part of the + // UserAuthentication + @Secured({ "ROLE_USER" }) + @PostMapping(value = "/api/admin/user/profile") + public ResponseEntity setProfile(@RequestBody ProfileDTO profile, Principal principal) { + UserAuthentication ua; + try { + ua = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + ua.setProfile(this.userMapper.toProfile(profile)); + userAuthenticationRepository.save(ua); + + return new ResponseEntity<>("Profile saved", HttpStatus.OK); + } + + /// Roles /// + + /*** + * get all the roles in system + * + * @return + */ + @Secured({ "ROLE_ADMIN" }) + @GetMapping(value = "/api/admin/roles") + @ResponseBody + public Collection getAllUserRole() { + logger.info("/api/admin/roles"); + return IterableUtils.toList(roleRepository.findAll()); + } + + /** + * Get all the roles for the current user + * + * @return + */ + @Secured({ "ROLE_USER" }) + @GetMapping(value = "/api/admin/user/roles") + public ResponseEntity getUserRolesForUser(Principal principal) { + + logger.info("/api/admin/user/roles"); + + UserAuthentication ua; + try { + ua = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + List roles = ua.getRoles(); + logger.debug("user auth roles: {}", roles); + + Collection ret = roleRepository.findByNameIn(roles); + logger.debug("user roles: {}", ret); + + return new ResponseEntity<>(ret, HttpStatus.OK); + } + + /*** + * add user to a role by role name + * + * @param roleName + * @return + */ + @Secured({ "ROLE_ADMIN" }) + @GetMapping(value = "/api/admin/user/role/{roleName}") + public ResponseEntity addUserRoleToUser(@PathVariable("roleName") String roleName, Principal principal) { + logger.info("/api/admin/user/role/{}", roleName); + + UserAuthentication ua; + try { + ua = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + UserRole role = null; + try { + role = roleRepository.findByName(roleName); + } catch (NoSuchElementException ex) { + return new ResponseEntity<>("No such role exists under this name", HttpStatus.BAD_REQUEST); + } + + role.addUser(ua); + roleRepository.save(role); + return new ResponseEntity<>(role, HttpStatus.OK); + } + + /*** + * add a privilege to a role + * + * @param roleName + * @param privilegeName + * @return + */ + @Secured({ "ROLE_ADMIN" }) + @GetMapping(value = "/api/admin/role/{roleName}/privilege/{privilegeName}") + @ResponseBody + public UserRole addPrivilegeToRole(@PathVariable("roleName") String roleName, + @PathVariable("privilegeName") String privilegeName) { + logger.info("/api/admin/role/{}/privilege/{}", roleName, privilegeName); + UserRole role = roleRepository.findByName(roleName); + Privilege priv = privilegeRepository.findByName(privilegeName); + role.addPrivilege(priv); + roleRepository.save(role); + return role; + } + + @Secured({ "ROLE_ADMIN" }) + @DeleteMapping(value = "/api/admin/role/{roleName}/privilege/{privilegeName}") + @ResponseBody + public UserRole removePrivilegeFromRole(@PathVariable("roleName") String roleName, + @PathVariable("privilegeName") long privilegeName) { + logger.info("/api/admin/role/{}/privilege/{}", roleName, privilegeName); + UserRole role = roleRepository.findByName(roleName); + role.removePrivilege(privilegeName); + roleRepository.save(role); + return role; + } + + /*** + * get a role + * + * @param roleName + * @return + */ + @Secured({ "ROLE_ADMIN" }) + @GetMapping(value = "/api/admin/role/{roleName}") + @ResponseBody + public UserRole getUserRole(@PathVariable("roleName") String roleName) { + logger.info("/api/admin/role/{}", roleName); + return roleRepository.findByName(roleName); + } + + @Secured({ "ROLE_ADMIN" }) + @PostMapping(value = "/api/admin/role/", produces = { MediaType.TEXT_PLAIN_VALUE }) + @ResponseBody + public UserRole addRole(@RequestBody UserRoleDTO role) { + logger.info("/api/admin/role/ addRole"); + + try { + UserRole exist = roleRepository.findByName(role.getName()); + + if (exist == null) { + return roleRepository.save(this.userMapper.toUserRole(role)); + } + } catch (Exception ex) { + logger.error(ERROR_DELETE_MESSAGE, ex); + } + return null; + } + + @Secured({ "ROLE_ADMIN" }) + @DeleteMapping(value = "/api/admin/role/{id}") + public ResponseEntity deleteRole(@PathVariable("id") long id) { + logger.debug("/api/admin/role/{} DELETE", id); + try { + roleRepository.deleteById(id); + return ResponseEntity.ok().body(String.format("role '%s' deleted.", id)); + + } catch (Exception ex) { + logger.error("Error deleting role", ex); + } + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error deleting role"); + } + + /// PRIVILEGES /// + + /*** + * get the privileges for the current user + * + * @return + */ + @Secured({ "ROLE_USER" }) + @GetMapping(value = "/api/admin/user/privileges") + public ResponseEntity getUserPrivileges(Principal principal) { + UserAuthentication ua; + try { + ua = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + List roles = roleRepository.findByNameIn(ua.getRoles()); + List privs = new ArrayList<>(); + + for (UserRole r : roles) { + privs.addAll(r.getPrivilegeList()); + } + return new ResponseEntity<>(privs, HttpStatus.OK); + } + + /*** + * get list of all the available privileges + * + * @return + */ + @Secured({ "ROLE_ADMIN" }) + @GetMapping(value = "/api/admin/privilege/") + @ResponseBody + public Collection getAllPrivileges() { + logger.info("/api/admin/privilege/"); + return IterableUtils.toList(privilegeRepository.findAll()); + } + + @Secured({ "ROLE_ADMIN" }) + @GetMapping(value = "/api/admin/privilege/{name}") + @ResponseBody + public Privilege getPrivilege(@PathVariable("name") String name) { + logger.info("/api/admin/privilege/name {}", name); + return privilegeRepository.findByName(name); + } + + @Secured({ "ROLE_ADMIN" }) + @ResponseBody + @PostMapping(value = "/api/admin/privilege/", produces = { MediaType.TEXT_PLAIN_VALUE }) + public Privilege addPrivilege(@RequestBody PrivilegeDTO priv) { + return privilegeRepository.save(this.userMapper.toPrivilege(priv)); + } + + @Secured({ "ROLE_ADMIN" }) + @DeleteMapping(value = "/api/admin/privilege/{id}") + public ResponseEntity deletePrivilege(@PathVariable("id") long id) { + logger.debug("/api/admin/priviledge/{} DELETE", id); + try { + privilegeRepository.deleteById(id); + return ResponseEntity.ok().body(String.format("privilege '%s' deleted.", id)); + + } catch (Exception ex) { + logger.error(ERROR_DELETE_MESSAGE, ex); + } + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ERROR_DELETE_MESSAGE); + } + +} diff --git a/web/src/main/java/org/mitre/iwp/web/rest/DataLoader.java b/web/src/main/java/org/mitre/iwp/web/rest/DataLoader.java new file mode 100644 index 0000000..9a650de --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/rest/DataLoader.java @@ -0,0 +1,236 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.rest; + +import org.mitre.iwp.web.IwpWebProperties; +import org.mitre.iwp.web.model.*; +import org.mitre.iwp.web.service.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +@Component +public class DataLoader implements ApplicationRunner { + private static final Logger log = LoggerFactory.getLogger(DataLoader.class); + + private UserAuthenticationRepository userAuthenticationRepository; + private PrivilegeRepository privilegeRepository; + private UserRoleRepository userRoleRepository; + private IwpWebProperties iwpWebProperties; + + private static final String CREATE_SEARCH_STRING = "CREATE_SEARCH"; + private static final String CREATE_REVIEW_STRING = "CREATE_REVIEW"; + private static final String CASE_REQ_SUBMIT_STRING = "CASE_REQ_SUBMIT"; + private static final String ADD_COMMENT_STRING = "ADD_COMMENT"; + private static final String ROLE_USER_STRING = "ROLE_USER"; + private static final String ROLE_CASE_WORKER_STRING = "ROLE_CASE_WORKER"; + private static final String ROLE_CASE_SUPER_STRING = "ROLE_CASE_SUPERVISOR"; + + //Default admin values + + @Value("${iwp.users.defaultAdminFullName}") + private String defaultAdminFullName; + + @Value("${iwp.users.defaultAdminTitle}") + private String defaultAdminTitle; + + @Value("${iwp.users.defaultAdminEmail}") + private String defaultAdminEmail; + + @Value("${iwp.users.defaultAdminUsername}") + private String defaultAdminUsername; + + @Value("${iwp.users.defaultAdminPassword}") + private String defaultAdminPassword; + + //Default reviewer values + + @Value("${iwp.users.defaultReviewerFullName}") + private String defaultReviewerFullName; + + @Value("${iwp.users.defaultReviewerTitle}") + private String defaultReviewerTitle; + + @Value("${iwp.users.defaultReviewerEmail}") + private String defaultReviewerEmail; + + @Value("${iwp.users.defaultReviewerUsername}") + private String defaultReviewerUsername; + + @Value("${iwp.users.defaultReviewerPassword}") + private String defaultReviewerPassword; + + //Default supervisor values + + @Value("${iwp.users.defaultSupervisorFullName}") + private String defaultSupervisorFullName; + + @Value("${iwp.users.defaultSupervisorTitle}") + private String defaultSupervisorTitle; + + @Value("${iwp.users.defaultSupervisorEmail}") + private String defaultSupervisorEmail; + + @Value("${iwp.users.defaultSupervisorUsername}") + private String defaultSupervisorUsername; + + @Value("${iwp.users.defaultSupervisorPassword}") + private String defaultSupervisorPassword; + + @Autowired + public DataLoader(UserAuthenticationRepository userAuthenticationRepository, + PrivilegeRepository privilegeRepository, + UserRoleRepository userRoleRepository, + IwpWebProperties iwpWebProperties){ + this.userAuthenticationRepository = userAuthenticationRepository; + this.privilegeRepository = privilegeRepository; + this.userRoleRepository = userRoleRepository; + this.iwpWebProperties = iwpWebProperties; + } + + @Override + public void run(ApplicationArguments args) { + if(this.privilegeRepository.count() == 0){ + log.info("Adding Privileges"); + this.privilegeRepository.save(new Privilege("USER_EDIT", "Can add,edit,remove Users from system")); + this.privilegeRepository.save(new Privilege("CASE_EDIT","Can create and edit Cases")); + this.privilegeRepository.save(new Privilege("CASE_DELETE","Can delete Cases")); + this.privilegeRepository.save(new Privilege("CASE_REPORTS","Can generate reports on Cases")); + this.privilegeRepository.save(new Privilege(CREATE_SEARCH_STRING,"Can create, edit and submit a search, not associated with any Case")); + this.privilegeRepository.save(new Privilege(CREATE_REVIEW_STRING,"Can create, edit and submit a review, not associated with any Case")); + this.privilegeRepository.save(new Privilege("CASE_REQ_ASSIGN_ANY","Can assign Case Request to self or others")); + this.privilegeRepository.save(new Privilege("CASE_REQ_ASSIGN_REVIEW_TO_ME","Can assign unassigned Review Case Requests to self")); + this.privilegeRepository.save(new Privilege("CASE_REQ_ASSIGN_SEARCH_TO_ME","Can assign unassigned Search Case Requests to self")); + this.privilegeRepository.save(new Privilege("CASE_REQ_EDIT","Can edit Case Requests")); + this.privilegeRepository.save(new Privilege("CASE_REQ_ADD","Can add a Case Request to Case")); + this.privilegeRepository.save(new Privilege(CASE_REQ_SUBMIT_STRING,"Can Submit a Case Request")); + this.privilegeRepository.save(new Privilege("CASE_REQ_CLOSE","Can close a Case Request")); + this.privilegeRepository.save(new Privilege("CASE_REQ_DELETE","Can delete a Case Request")); + this.privilegeRepository.save(new Privilege(ADD_COMMENT_STRING,"Can add comment to a Case")); + this.privilegeRepository.save(new Privilege("DELETE_COMMENT","Can delete own comment from Case")); + this.privilegeRepository.save(new Privilege("DELETE_COMMENT_ANY","Can delete others comments from Case")); + } + + if(this.userRoleRepository.count() == 0){ + log.info("Adding Use Roles"); + UserRole user = new UserRole(); + user.setName(ROLE_USER_STRING); + user.setDescription("Everyone"); + this.userRoleRepository.save(user); + + UserRole userCaseWorker = new UserRole(); + userCaseWorker.setName(ROLE_CASE_WORKER_STRING); + userCaseWorker.setDescription("Case Worker"); + userCaseWorker.getPrivileges().add(this.privilegeRepository.findByName(CREATE_REVIEW_STRING)); + userCaseWorker.getPrivileges().add(this.privilegeRepository.findByName(CREATE_SEARCH_STRING)); + userCaseWorker.getPrivileges().add(this.privilegeRepository.findByName("CASE_REQ_ASSIGN_REVIEW_TO_ME")); + userCaseWorker.getPrivileges().add(this.privilegeRepository.findByName("CASE_REQ_ASSIGN_SEARCH_TO_ME")); + userCaseWorker.getPrivileges().add(this.privilegeRepository.findByName(CASE_REQ_SUBMIT_STRING)); + userCaseWorker.getPrivileges().add(this.privilegeRepository.findByName(ADD_COMMENT_STRING)); + userCaseWorker.getPrivileges().add(this.privilegeRepository.findByName("DELETE_COMMENT")); + this.userRoleRepository.save(userCaseWorker); + + UserRole userAdmin = new UserRole(); + userAdmin.setName("ROLE_ADMIN"); + userAdmin.setDescription("Administrator"); + userAdmin.getPrivileges().add(this.privilegeRepository.findByName("USER_EDIT")); + this.userRoleRepository.save(userAdmin); + + UserRole userSuper = new UserRole(); + userSuper.setName(ROLE_CASE_SUPER_STRING); + userSuper.setDescription("Case Supervisors"); + userSuper.getPrivileges().add(this.privilegeRepository.findByName("CASE_EDIT")); + userSuper.getPrivileges().add(this.privilegeRepository.findByName("CASE_DELETE")); + userSuper.getPrivileges().add(this.privilegeRepository.findByName("CASE_REPORTS")); + userSuper.getPrivileges().add(this.privilegeRepository.findByName(CREATE_REVIEW_STRING)); + userSuper.getPrivileges().add(this.privilegeRepository.findByName(CREATE_SEARCH_STRING)); + userSuper.getPrivileges().add(this.privilegeRepository.findByName("CASE_REQ_ASSIGN_ANY")); + userSuper.getPrivileges().add(this.privilegeRepository.findByName("CASE_REQ_EDIT")); + userSuper.getPrivileges().add(this.privilegeRepository.findByName("CASE_REQ_ADD")); + userSuper.getPrivileges().add(this.privilegeRepository.findByName(CASE_REQ_SUBMIT_STRING)); + userSuper.getPrivileges().add(this.privilegeRepository.findByName("CASE_REQ_CLOSE")); + userSuper.getPrivileges().add(this.privilegeRepository.findByName("CASE_REQ_DELETE")); + userSuper.getPrivileges().add(this.privilegeRepository.findByName(ADD_COMMENT_STRING)); + userSuper.getPrivileges().add(this.privilegeRepository.findByName("DELETE_COMMENT_ANY")); + + this.userRoleRepository.save(userSuper); + } + + if(this.userAuthenticationRepository.count() == 0){ + log.info("Adding User Authentication"); + + UserAuthentication userTest = new UserAuthentication(); + log.debug("CREATING DEFAULT ADMIN USER WITH defaultAdminUsername: {}, defaultAdminFullName: {}, defaultAdminTitle: {}, defaultAdminEmail: {}", defaultAdminUsername, defaultAdminFullName, defaultAdminTitle, defaultAdminEmail); + userTest.setUsername(defaultAdminUsername); + userTest.setPassword(defaultAdminPassword); + userTest.setProfile(getDefaultProfile(defaultAdminUsername, defaultAdminFullName, defaultAdminTitle, defaultAdminEmail)); + //"ROLE_USER", "ROLE_CASE_WORKER", "ROLE_ADMIN", "ROLE_CASE_SUPERVISOR" + userTest.getRoles().add(ROLE_USER_STRING); + userTest.getRoles().add(ROLE_CASE_WORKER_STRING); + userTest.getRoles().add("ROLE_ADMIN"); + userTest.getRoles().add(ROLE_CASE_SUPER_STRING); + this.userAuthenticationRepository.save(userTest); + + UserAuthentication userReviewer = new UserAuthentication(); + log.debug("CREATING DEFAULT REVIEWER USER WITH defaultReviewerUsername: {}, defaultReviewerFullName: {}, defaultReviewerTitle: {}, defaultReviewerEmail: {}", defaultReviewerUsername, defaultReviewerFullName, defaultReviewerTitle, defaultReviewerEmail); + userReviewer.setUsername(defaultReviewerUsername); + userReviewer.setPassword(defaultReviewerPassword); + userReviewer.setProfile(getDefaultProfile(defaultReviewerUsername, defaultReviewerFullName, defaultReviewerTitle, defaultReviewerEmail)); + //"ROLE_USER","ROLE_CASE_WORKER" + userReviewer.getRoles().add(ROLE_USER_STRING); + userReviewer.getRoles().add(ROLE_CASE_WORKER_STRING); + this.userAuthenticationRepository.save(userReviewer); + + UserAuthentication userSuper = new UserAuthentication(); + log.debug("CREATING DEFAULT SUPERVISOR USER WITH defaultSupervisorUsername: {}, defaultSupervisorFullName: {}, defaultSupervisorTitle: {}, defaultSupervisorEmail: {}", defaultSupervisorUsername, defaultSupervisorFullName, defaultSupervisorTitle, defaultSupervisorEmail); + userSuper.setUsername(defaultSupervisorUsername); + userSuper.setPassword(defaultSupervisorPassword); + userSuper.setProfile(getDefaultProfile(defaultSupervisorUsername, defaultSupervisorFullName, defaultSupervisorTitle, defaultSupervisorEmail)); + //"ROLE_USER","ROLE_CASE_SUPERVISOR" + userSuper.getRoles().add(ROLE_USER_STRING); + userSuper.getRoles().add(ROLE_CASE_SUPER_STRING); + this.userAuthenticationRepository.save(userSuper); + } + + + } + + private UserSetting getDefaultUserSetting(){ + UserSetting us = new UserSetting(); + us.setStrokeWidth(this.iwpWebProperties.getProfileStrokeWidth()); + us.setDrawColor(this.iwpWebProperties.getProfileDrawColor()); + us.setFillColor(this.iwpWebProperties.getProfileFillColor()); + us.setNumHistory(this.iwpWebProperties.getProfileNumberHistory()); + return us; + } + + private Profile getDefaultProfile(String name, String fullName, String title, String email){ + Profile prof = new Profile(); + prof.setUserSetting(this.getDefaultUserSetting()); + prof.setName(name); + prof.setFullName(fullName); + prof.setTitle(title); + prof.setEmail(email); + prof.setDestinationAgencyIdentifier(this.iwpWebProperties.getProfileDai()); + prof.setOriginatingAgencyIdentifier(this.iwpWebProperties.getProfileOri()); + prof.setAttentionIndicator(this.iwpWebProperties.getProfileAttIndicator()); + prof.setSourceAgency(this.iwpWebProperties.getProfileSrcAgency()); + prof.setIsReportExclude(this.iwpWebProperties.getIsProfileReportExclude()); + prof.setDepartment(this.iwpWebProperties.getProfileDpt()); + prof.setAddress(this.iwpWebProperties.getProfileAdd()); + + return prof; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/rest/EbtsController.java b/web/src/main/java/org/mitre/iwp/web/rest/EbtsController.java new file mode 100644 index 0000000..864f60d --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/rest/EbtsController.java @@ -0,0 +1,365 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.rest; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.mitre.iwp.web.dto.EbtsMapper; +import org.mitre.iwp.web.dto.PersonDTO; +import org.mitre.iwp.web.dto.ProfileDTO; +import org.mitre.iwp.web.dto.UserMapper; +import org.mitre.iwp.web.exception.InvalidMessageException; +import org.mitre.iwp.web.model.*; +import org.mitre.iwp.web.security.IwpAuthService; +import org.mitre.iwp.web.service.IrisService; +import org.mitre.iwp.web.service.ReportService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.security.Principal; +import java.util.*; +import java.awt.image.BufferedImage; + +import javax.imageio.ImageIO; + +@RestController +public class EbtsController { + private static final Logger log = LoggerFactory.getLogger(EbtsController.class); + + private final IrisService irisService; + + private final ReportService reportService; + + private final IwpAuthService authService; + + private final EbtsMapper ebtsMapper; + + private final UserMapper userMapper; + + private static final String APP_OCT_STRING = "application/octet-stream"; + + public EbtsController(IrisService irisService, ReportService reportService, + IwpAuthService authService, EbtsMapper ebtsMapper, UserMapper userMapper) { + this.irisService = irisService; + this.reportService = reportService; + this.authService = authService; + this.ebtsMapper = ebtsMapper; + this.userMapper = userMapper; + } + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @PostMapping(value = "/api/image") + public ResponseEntity uploadImage(@RequestParam("file") MultipartFile mpFile) throws IOException { + try { + return ResponseEntity.ok() + .body(String + .valueOf(irisService.uploadImage(mpFile.getContentType(), mpFile.getBytes(), "", true))); + } catch (Exception exception) { + logError(exception); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(exception.getMessage()); + } + } + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @PostMapping(value = "/api/iris/create") + public ResponseEntity uploadCreateImage(MultipartHttpServletRequest request, Principal principal) { + UserAuthentication auth; + Gson gson = new Gson(); + try { + auth = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + CreateIrisSearchRequest response = new CreateIrisSearchRequest(); + try { + Iterator itr = request.getFileNames(); + log.info("Uploading Create image"); + while (itr.hasNext()) { + String uploadedFile = itr.next(); + MultipartFile file = request.getFile(uploadedFile); + if (file != null) { + String mimeType = file.getContentType(); + byte[] bytes = file.getBytes(); + response = irisService.submitCreateImage(mimeType, bytes, auth.getId()); + } else { + response.getMessage().addErrorMessages(String.format("Unable to process file %s", uploadedFile)); + } + } + } catch (Exception e) { + if (response.getMessage() == null) { + response.setMessage(new EbtsMessageRequest()); + } + + response.getMessage().addErrorMessages(e.getMessage()); + } + + return ResponseEntity.ok().body(gson.toJson(response)); + } + + @Secured({ "ROLE_USER" }) + @GetMapping(value = "/api/image/{id}") + public ResponseEntity getImage(@PathVariable("id") long id) { + try { + return ResponseEntity.ok().contentType(MediaType.IMAGE_PNG).body(irisService.getImage(id)); + } catch (Exception e) { + log.error(e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.IMAGE_PNG).body(null); + } + } + + @Secured({ "ROLE_USER" }) + @GetMapping(value = "/api/image/thumb/{id}") + public ResponseEntity getImageThumbnail(@PathVariable("id") long id) { + try { + return ResponseEntity.ok().contentType(MediaType.IMAGE_PNG).body(irisService.getImageThumbnail(id)); + } catch (Exception e) { + log.error(e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.IMAGE_PNG).body(null); + } + } + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/iris/response/{srb}") + public ResponseEntity getResponseForSrbPath(@PathVariable("srb") String srb, Principal principal) { + log.info("/api/iris/response/{}", srb); + + UserAuthentication auth; + try { + auth = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + ReportData returnData = this.reportService.getReportByFileName(srb); + + if (null != returnData) { + Gson gson = new GsonBuilder().create(); + return ResponseEntity.ok().body(gson.toJson(returnData)); + } + try { + File f = new File(srb); + byte[] srbFile = irisService.getSrbData(srb); + ReportData response = irisService.submitSrb(srbFile, auth.getId(), f.getName()); + Gson gson = new GsonBuilder().create(); + return ResponseEntity.ok().body(gson.toJson(response)); + } catch (Exception e) { + logError(e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Error getting response for srb " + srb); + } + } + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @PostMapping(value = "/api/iris/response") + public ResponseEntity submitResponse(@RequestParam("file") MultipartFile mpFile, Principal principal) { + UserAuthentication auth; + try { + auth = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + ReportData response = new ReportData(); + Gson gson = new GsonBuilder().create(); + log.info("/api/iris/response"); + try { + String contentType = mpFile.getContentType(); + String mpFileName = mpFile.getOriginalFilename(); + if (contentType == null) { + response.getErrorMessages().add("Unknown File Content Type"); + } else { + byte[] srbFile = mpFile.getBytes(); + log.info("/api/iris/response contentType:{} bytes:{}", contentType, srbFile.length); + if (contentType.equals(APP_OCT_STRING)) { + if (irisService.isMultiModal(srbFile)) { + List iDataList = irisService.createImageDataFromEbts(srbFile); + return ResponseEntity.ok().body(gson.toJson(iDataList)); + } else { + response = irisService.submitSrb(srbFile, auth.getId(), mpFileName); + } + } else if (contentType.equals("text/xml") || contentType.equals("application/xml")) { + response = irisService.submitIrisXmlRequest(srbFile, auth.getId(), mpFileName); + } else if (contentType.startsWith("image")) { + List iDataList = new ArrayList<>(); + if (mpFileName != null) { + String mpFileExtension = mpFileName.substring(mpFileName.lastIndexOf(".") + 1); + iDataList.add( + this.irisService.createImageData(contentType, checkImageType(mpFile, mpFileExtension))); + return ResponseEntity.ok().body(gson.toJson(iDataList)); + } else { + log.error("Original filename is null!"); + throw new IOException("Original filename is null!"); + } + } + } + } catch (Exception e) { + log.info(e.getMessage()); + response.addErrorMessages(e.getMessage()); + } + + return ResponseEntity.ok().body(gson.toJson(response)); + }// end of submitSrb + + public byte[] checkImageType(MultipartFile mpFile, String fileExtension) throws IOException { + // Read the multipart file into a BufferedImage + BufferedImage img = ImageIO.read(mpFile.getInputStream()); + + // Check whether the image loaded successfully. + if (img != null) { + // Get and check the image-data type. + // If the image is 16-bit unsigned (ushort) + int type = img.getType(); + if (type == BufferedImage.TYPE_USHORT_GRAY) { + // Convert to 8-bit grayscale + int width = img.getWidth(); + int height = img.getHeight(); + + // TO CONVERT TO TYPE_BYTE_GRAY + byte[] img8bit = new byte[width * height]; + + int index = 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int pixel = img.getRaster().getSample(x, y, 0); + // Normalize the 16-bit value to 8-bit + byte normalizedPixel = (byte) (((long) pixel * 255) / 65535); + img8bit[index++] = normalizedPixel; + } + } + + // img8bit is now an 8-bit representation of your original image + try { + BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); + newImage.getRaster().setDataElements(0, 0, width, height, img8bit); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ImageIO.write(newImage, fileExtension, outputStream); + return outputStream.toByteArray(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + } else { + log.error("Error opening image!"); + throw new IOException("Error opening image!"); + } + return mpFile.getBytes(); + } + + @Secured({ "ROLE_CASE_SUPERVISOR" }) + @PostMapping(value = "/api/caseinfo") + public ResponseEntity uploadPerson(@RequestBody PersonDTO person) { + try { + return ResponseEntity.ok() + .body(String.valueOf(this.reportService.savePerson(this.ebtsMapper.toPerson(person)))); + } catch (Exception exception) { + logError(exception); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(exception.getMessage()); + } + } + + @Secured({ "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/caseinfo/persons") + public ResponseEntity listPersons() { + List response = this.reportService.listPersons(); + Gson gson = new GsonBuilder().create(); + return ResponseEntity.ok().body(gson.toJson(response)); + } + + @Secured({ "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/caseinfo/persons/{id}") + public ResponseEntity getPerson(@PathVariable("id") long id) { + Person person = this.reportService.getPerson(id); + Gson gson = new GsonBuilder().create(); + return ResponseEntity.ok().body(gson.toJson(person)); + } + + @Secured({ "ROLE_CASE_SUPERVISOR" }) + @PostMapping(value = "/api/caseinfo/delete/person") + public ResponseEntity deletePerson(@RequestBody PersonDTO person) { + this.reportService.removePerson(person.getId()); + return ResponseEntity.ok().body(String.format("Person '%s' deleted.", person.getId())); + } + + @Secured({ "ROLE_CASE_WORKER" }) + @PostMapping(value = "/api/service/tshepii/compare") + public ResponseEntity getTshepiiCompare(@RequestBody Map msg) { + List responseList = new ArrayList<>(); + TshepiiResponse response = new TshepiiResponse(); + Gson gson = new GsonBuilder().create(); + + responseList.add(response); + return ResponseEntity.ok().body(gson.toJson(responseList)); + } + + @Secured({ "ROLE_CASE_WORKER" }) + @PostMapping(value = "/api/service/tshepii/compareResponses") + public ResponseEntity getTshepiiCompareFromResponse(@RequestBody Map msg) { + List responseList = new ArrayList<>(); + TshepiiResponse response = new TshepiiResponse(); + Gson gson = new GsonBuilder().create(); + + responseList.add(response); + return ResponseEntity.ok().body(gson.toJson(responseList)); + } + + @Secured({ "ROLE_ADMIN" }) + @PostMapping(value = "/api/iris/create-profile") + public ResponseEntity createProfile(@RequestBody ProfileDTO profile) throws InvalidMessageException { + long response = irisService.createProfile(this.userMapper.toProfile(profile)); + Gson gson = new GsonBuilder().create(); + return ResponseEntity.ok().body(gson.toJson(response)); + } + + @Secured({ "ROLE_ADMIN" }) + @GetMapping(value = "/api/iris/profiles") + public ResponseEntity listProfiles() { + List response = irisService.listProfiles(); + Gson gson = new GsonBuilder().create(); + return ResponseEntity.ok().body(gson.toJson(response)); + } + + @Secured({ "ROLE_ADMIN" }) + @PostMapping(value = "/api/iris/update-profile") + public ResponseEntity updateProfile(@RequestBody ProfileDTO profile) throws InvalidMessageException { + long response = irisService.updateProfile(this.userMapper.toProfile(profile)); + Gson gson = new GsonBuilder().create(); + return ResponseEntity.ok().body(gson.toJson(response)); + } + + @Secured({ "ROLE_ADMIN" }) + @PostMapping(value = "/api/iris/deleteProfile/{id}") + public ResponseEntity removeProfile(@PathVariable("id") long id) throws InvalidMessageException { + irisService.deleteProfile(id); + return ResponseEntity.ok().body(String.format("Profile '%s' deleted.", id)); + } + + private void logError(Exception exc) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + exc.printStackTrace(pw); + log.error("Error: {}", sw); + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/rest/IrisSearchController.java b/web/src/main/java/org/mitre/iwp/web/rest/IrisSearchController.java new file mode 100644 index 0000000..faad7ad --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/rest/IrisSearchController.java @@ -0,0 +1,360 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.rest; + +import com.google.common.net.HttpHeaders; +import org.mitre.iwp.web.dto.CreateIrisSearchRequestDTO; +import org.mitre.iwp.web.dto.ImageMessageMapper; +import org.mitre.iwp.web.exception.InvalidMessageException; +import org.mitre.iwp.web.model.*; +import org.mitre.iwp.web.security.IwpAuthService; +import org.mitre.iwp.web.service.AnnotationRepository; +import org.mitre.iwp.web.service.AnnotationValueRepository; +import org.mitre.iwp.web.service.CreateIrisSearchRequestRepository; +import org.mitre.iwp.web.service.IrisService; +import org.mitre.iwp.web.service.ReportDataService; +import org.mitre.jet.exceptions.EbtsBuildingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.web.bind.annotation.*; + +import java.io.*; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.*; + +@RestController +public class IrisSearchController { + + private static final Logger logger = LoggerFactory.getLogger(IrisSearchController.class); + + private final IrisService irisService; + + private final CreateIrisSearchRequestRepository createIrisSearchRequestRepository; + + private final IwpAuthService authService; + + private final ImageMessageMapper imageMessageMapper; + + private final AnnotationValueRepository avRepository; + + private final ReportDataService reportDataService; + + private final AnnotationRepository annotationRepository; + + + public IrisSearchController(IrisService irisService, + CreateIrisSearchRequestRepository createIrisSearchRequestRepository, IwpAuthService authService, + ImageMessageMapper imageMessageMapper, AnnotationValueRepository avRepository, ReportDataService reportDataService, AnnotationRepository annotationRepository) { + this.irisService = irisService; + this.createIrisSearchRequestRepository = createIrisSearchRequestRepository; + this.authService = authService; + this.imageMessageMapper = imageMessageMapper; + this.avRepository = avRepository; + this.reportDataService = reportDataService; + this.annotationRepository = annotationRepository; + } + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/iris/search/count") + public ResponseEntity getSearchRequestCount(Principal principal) { + UserAuthentication ua; + try { + ua = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + Long count = this.createIrisSearchRequestRepository.countAllByUser_Id(ua.getId()); + return new ResponseEntity<>(count, HttpStatus.OK); + } + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/iris/search/image") + public ResponseEntity getSearchRequestImage(@RequestParam(value = "id") long id) { + Optional cisrOption = createIrisSearchRequestRepository.findById(id); + if (cisrOption.isPresent()) { + CreateIrisSearchRequest request = cisrOption.get(); + byte[] imageBytes = request.getMessage().getImageData().getImageBytes(); + return ResponseEntity.ok().contentType(MediaType.IMAGE_PNG).body(imageBytes); + } + return ResponseEntity.notFound().build(); + } + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/iris/search/list") + @ResponseBody + public ResponseEntity getCreateIrisSearchMessages( + @RequestParam(value = "start", defaultValue = "0") Integer start, + @RequestParam(value = "limit", defaultValue = "-1") Integer limit, + @RequestParam(value = "sort", defaultValue = "creationDateEpoch") String sortColumn, + @RequestParam(value = "direction", defaultValue = "desc") String direction, Principal principal) { + + Sort.Direction sortDirection = Sort.Direction.DESC; + if (direction.equalsIgnoreCase("asc")) { + sortDirection = Sort.Direction.ASC; + } + + UserAuthentication ua; + try { + ua = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + if (limit == -1) { + limit = Integer.MAX_VALUE; + } + + logger.info("Pulling Search Requests for userId: {}", ua.getId()); + List returnList = this.createIrisSearchRequestRepository.findByUser_Id(ua.getId(), + PageRequest.of(start, limit, Sort.by(sortDirection, sortColumn))); + logger.info("Return list size: {}", returnList.size()); + + return ResponseEntity.ok().body(returnList); + } + + // Creates an EBTS search message from a previously created + // CreateIrisSearchRequest. Used on the history page. + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/iris/search/download/{id}") + public ResponseEntity downloadIrisSearchMessage(@PathVariable(value = "id") long id, Principal principal) { + logger.info("/api/iris/search/download Trying to download file with id: {}", id); + try{ + String filePath = this.findReportById(id); + try(FileInputStream fis = new FileInputStream(filePath)) { + byte[] doc = new byte[fis.available()]; + if(fis.read(doc) <= 0) { + throw new IOException(String.format("Unable to read file %s", filePath)); + } + Resource file = new ByteArrayResource(doc); + + if (doc.length == 0) { + return ResponseEntity.badRequest().body("Error Pulling Report Info"); + } + + return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + new File(filePath).getName() + "\"") + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(file); + } + + } catch (Exception docEx) { + logger.info(docEx.getMessage()); + return ResponseEntity.badRequest().body(docEx.getMessage()); + } + } + + // creates a new create iris search message and saves it + private CreateIrisSearchRequest saveCreateIrisSearchMessage(CreateIrisSearchRequest createIrisSearchRequest) throws InvalidMessageException { + if (createIrisSearchRequest.getMessage() != null + && createIrisSearchRequest.getMessage().getImageData() != null) { + if (createIrisSearchRequest.getId() == null || createIrisSearchRequest.getId() == 0) { + createIrisSearchRequest.setCreationDateEpoch(Instant.now().getEpochSecond()); + } + + Optional originalCISR = createIrisSearchRequest.getId() != null ? this.createIrisSearchRequestRepository.findById(createIrisSearchRequest.getId()) : Optional.empty(); + + if(originalCISR.isPresent()) { + List annotations = originalCISR.get().getMessage().getImageData().getAnnotations(); + + Set collectedAnnotations = new HashSet<>(); + + // Gather all annotation IDs into a set. + for (Annotation a : annotations) { + reportDataService.collectAnnotationIds(collectedAnnotations, a); + } + + // Remove annotation values from the variable, then delete them from the database. + for (String annID : collectedAnnotations) { + if(this.annotationRepository.existsById(annID)){ + + Annotation currAnnotation = this.annotationRepository.getReferenceById(annID); + + if (currAnnotation.getValue() != null) { + long id = currAnnotation.getValue().getId(); + currAnnotation.setValue(null); + avRepository.deleteById(id); + avRepository.flush(); + } else { + currAnnotation.setValue(null); + } + + avRepository.flush(); + } + } + + // Dereference all annotations from the list and remove them from the database. + for (Iterator iterator = annotations.iterator(); iterator.hasNext(); ) { + + Annotation currAnnotation = iterator.next(); + String id = currAnnotation.getId(); + + logger.debug("Deleting annotation with value: {}", id); + + iterator.remove(); + + if(this.annotationRepository.existsById(id)) { + logger.debug("Deleting extra annotation from annotationRepository with value: {}", this.annotationRepository.getReferenceById(id).getId()); + this.annotationRepository.deleteById(this.annotationRepository.getReferenceById(id).getId()); + } + this.annotationRepository.flush(); + + } + + } + + logger.debug("createIrisSearchRequest annotations are: {}", createIrisSearchRequest.getMessage().getImageData().getAnnotations()); + + createIrisSearchRequest = this.createIrisSearchRequestRepository.save(createIrisSearchRequest); + + logger.debug("This CISR has the following annotations: {}", createIrisSearchRequest.getMessage().getImageData().getAnnotations()); + + logger.debug("Saved iris search request {}", createIrisSearchRequest.getId()); + return createIrisSearchRequest; + } else { + throw new InvalidMessageException("Invalid create message. There needs to be an image object"); + } + } + + // Saves a transaction that will be finished later + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @PostMapping(value = "/api/iris/search/save", consumes = { MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity saveIrisSearchMessage(@RequestBody CreateIrisSearchRequestDTO createIrisSearchRequestDTO, + Principal principal) throws InvalidMessageException { + CreateIrisSearchRequest tempCISR = this.imageMessageMapper.toCreateIrisSearchRequest(createIrisSearchRequestDTO); + + if (tempCISR.getMessage().getImageData().getImageData().length() == 0) { + logger.info("Image data is Empty"); + } + + UserAuthentication ua; + try { + ua = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + this.checkSetUserAuthentication(tempCISR, ua); + return ResponseEntity.ok().body(this.saveCreateIrisSearchMessage(tempCISR)); + } + + // creates a 'CreateIrisSearchRequest' and builds an ebts search request from it + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @PostMapping(value = "/api/iris/search", consumes = { MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity createEbtsMessage(@RequestBody CreateIrisSearchRequestDTO createIrisSearchRequest, + Principal principal) throws NoSuchAlgorithmException, InvalidMessageException, IOException, EbtsBuildingException { + + logger.info("/api/iris/search POST creating iris message"); + + CreateIrisSearchRequest tempCISR = this.imageMessageMapper.toCreateIrisSearchRequest(createIrisSearchRequest); + EbtsMessageRequest tempMessage = tempCISR.getMessage(); + tempMessage.setDateOfSubmission(new Date().toString()); + createIrisSearchRequest.setMessage(tempMessage); + + UserAuthentication ua; + + try { + ua = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + this.checkSetUserAuthentication(tempCISR, ua); + + byte[] ebtsFileBytes = this.irisService.storeEbtsFile(tempCISR); + + try { + this.saveCreateIrisSearchMessage(tempCISR); + } catch (Exception e) { + logger.error("/api/iris/search Error while saving iris search message: {}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body("Invalid create message. There needs to be an image id"); + } + + + if (ebtsFileBytes.length != 0) { + String filename = new SimpleDateFormat("'generatedEbts_'yyyyMMddHHmm'.eft'").format(new Date()); + ByteArrayResource file = new ByteArrayResource(ebtsFileBytes) { + @Override + public String getFilename() { + return filename; + } + }; + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"") + .contentType(MediaType.APPLICATION_OCTET_STREAM).body(file); + } + + logger.error("Invalid file"); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error creating file"); + + } + + private void checkSetUserAuthentication(CreateIrisSearchRequest tempCISR, UserAuthentication ua) { + if (tempCISR.getUser() == null || tempCISR.getUser().getId() == null) { + tempCISR.setUser(ua); + } + } + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/iris/search/{searchId}/submit") + public ResponseEntity submitSearch(@PathVariable(value = "searchId") long searchId) throws IOException, InvalidMessageException, EbtsBuildingException{ + logger.info("/api/iris/search/{}/submit ", searchId); + if (this.irisService.submitSearchRequest(searchId)) { + return ResponseEntity.status(HttpStatus.OK).body("OK"); + } + logger.error("Error in sending email"); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error in submitting"); + } + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/dev/iris/search/{id}") + public ResponseEntity getIrisSearchMessage(@PathVariable(value = "id") long id) { + logger.info("/dev/iris/search/{} ", id); + // get the message from mysql and check for username + Optional requestOptional = createIrisSearchRequestRepository.findById(id); + if (requestOptional.isPresent()) { + return ResponseEntity.ok().body(requestOptional.get()); + } + return ResponseEntity.notFound().build(); + } + + @Secured({ "ROLE_CASE_SUPERVISOR", "ROLE_CASE_WORKER" }) + @GetMapping(value = "/api/iris/search/find/{searchId}") + public ResponseEntity getSearch(@PathVariable("searchId") long searchId) { + Optional requestOptional = createIrisSearchRequestRepository.findById(searchId); + if (requestOptional.isPresent()) { + return ResponseEntity.ok().body(requestOptional.get()); + } + return ResponseEntity.notFound().build(); + } + + private String findReportById(Long reportId) throws InvalidMessageException { + // Look for the report by Id + CreateIrisSearchRequest searchRequest = createIrisSearchRequestRepository.findById(reportId).orElse(null); + if (searchRequest == null) { + throw new InvalidMessageException("Search Request cannot be found under this id."); + } + + return searchRequest.getFilePath(); + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/rest/NotificationController.java b/web/src/main/java/org/mitre/iwp/web/rest/NotificationController.java new file mode 100644 index 0000000..ba762ee --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/rest/NotificationController.java @@ -0,0 +1,107 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.rest; + +import com.google.gson.Gson; + +import org.mitre.iwp.web.dto.AppNotificationDTO; +import org.mitre.iwp.web.dto.UserMapper; +import org.mitre.iwp.web.model.AppNotification; +import org.mitre.iwp.web.service.AppNotificationRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import java.security.Principal; +import java.util.List; +import java.util.Optional; + +@RestController +public class NotificationController { + private static final Logger logger = LoggerFactory.getLogger(NotificationController.class); + private final AppNotificationRepository appNotificationRepository; + private final UserMapper userMapper; + + public NotificationController(AppNotificationRepository appNotificationRepository, + UserMapper userMapper){ + this.appNotificationRepository = appNotificationRepository; + this.userMapper = userMapper; + } + + @Secured({ "ROLE_CASE_SUPERVISOR", "ROLE_CASE_WORKER" }) + @GetMapping(value = "/api/notification/all/{userId}") + public ResponseEntity findAllNotificationsForUser(@PathVariable("userId") long userId, Principal principal){ + logger.debug("/api/notification/all/{}", userId); + Gson gson = new Gson(); + try { + List notList = this.appNotificationRepository.findByUserId(userId); + return new ResponseEntity<>(gson.toJson(notList), HttpStatus.OK); + }catch(Exception e){ + return ResponseEntity.badRequest().body(gson.toJson(e.getMessage())); + } + } + + @Secured({ "ROLE_CASE_SUPERVISOR", "ROLE_CASE_WORKER" }) + @GetMapping(value = "/api/notification/new/{userId}") + public ResponseEntity findAllNewNotificationsForUser(@PathVariable("userId") long userId, Principal principal){ + logger.debug("/api/notification/all/{}", userId); + Gson gson = new Gson(); + try { + List notList = this.appNotificationRepository.findByUserIdAndIsNew(userId, true); + return new ResponseEntity<>(gson.toJson(notList), HttpStatus.OK); + } catch(Exception e){ + return ResponseEntity.badRequest().body(gson.toJson(e.getMessage())); + } + } + + @Secured({ "ROLE_CASE_SUPERVISOR", "ROLE_CASE_WORKER" }) + @GetMapping(value = "/api/notification/markRead/{notificationId}") + public ResponseEntity markNotificationAsRead(@PathVariable("notificationId") long notificationId, Principal principal){ + logger.info("/api/notification/markRead/{}", notificationId); + Optional value = this.appNotificationRepository.findById(notificationId); + if(value.isPresent()) { + AppNotification appNotification = value.get(); + appNotification.setIsNew(false); + this.appNotificationRepository.save(appNotification); + return new ResponseEntity<>("OK", HttpStatus.OK); + } + + return new ResponseEntity<>("Id Not Found", HttpStatus.NOT_FOUND); + } + + @Secured({ "ROLE_CASE_SUPERVISOR", "ROLE_CASE_WORKER" }) + @PostMapping(value = "/api/notification/add") + public ResponseEntity addNotification(@RequestBody AppNotificationDTO appNotification, Principal principal){ + logger.info("/api/notification/add"); + Gson gson = new Gson(); + AppNotification tempAppNot = this.userMapper.toAppNotification(appNotification); + try { + tempAppNot = this.appNotificationRepository.save(tempAppNot); + return new ResponseEntity<>(gson.toJson(tempAppNot.getId()), HttpStatus.OK); + }catch(Exception e){ + return ResponseEntity.badRequest().body(gson.toJson(e.getMessage())); + } + } + + @Secured({ "ROLE_CASE_SUPERVISOR", "ROLE_CASE_WORKER" }) + @GetMapping(value = "/api/notification/delete/{notificationId}") + public ResponseEntity deleteNotification(@PathVariable("notificationId") long notificationId, Principal principal){ + logger.info("/api/notification/delete/{}", notificationId); + this.appNotificationRepository.deleteById(notificationId); + return new ResponseEntity<>("OK", HttpStatus.OK); + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/rest/ReportDataController.java b/web/src/main/java/org/mitre/iwp/web/rest/ReportDataController.java new file mode 100644 index 0000000..5223696 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/rest/ReportDataController.java @@ -0,0 +1,423 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.rest; + +import au.com.bytecode.opencsv.CSVWriter; +import com.google.common.net.HttpHeaders; +import com.google.gson.Gson; +import org.apache.commons.lang3.StringUtils; +import org.mitre.iwp.web.IwpWebProperties; +import org.mitre.iwp.web.dto.ImageMessageMapper; +import org.mitre.iwp.web.dto.ReportDataDTO; +import org.mitre.iwp.web.exception.InvalidMessageException; +import org.mitre.iwp.web.model.*; +import org.mitre.iwp.web.security.IwpAuthService; +import org.mitre.iwp.web.service.IrisService; +import org.mitre.iwp.web.service.ReportDataRepository; +import org.mitre.iwp.web.service.ReportService; +import org.mitre.iwp.web.service.ReportDataService; +import org.mitre.iwp.web.service.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.web.bind.annotation.*; + +import java.io.*; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.*; + +@RestController +public class ReportDataController { + private static final Logger logger = LoggerFactory.getLogger(ReportDataController.class); + + private final ReportService reportService; + + private final IrisService irisService; + + private final ReportDataRepository reportDataRepository; + + private final ReportResponseRepository reportResponseRepository; + + private final IwpAuthService authService; + + private final ReportDataService reportDataService; + + private final ImageMessageMapper imageMessageMapper; + + private final IwpWebProperties iwpWebProperties; + + private static final String ATTACH_NAME_STRING = "attachment; filename=\""; + + public ReportDataController(ReportService reportService, IrisService irisService, + ReportDataRepository reportDataRepository, + ReportResponseRepository reportResponseRepository, + ReportDataService reportDataService, IwpAuthService authService, + ImageMessageMapper imageMessageMapper, + IwpWebProperties iwpWebProperties) { + this.reportService = reportService; + this.irisService = irisService; + this.reportDataRepository = reportDataRepository; + this.authService = authService; + this.reportResponseRepository = reportResponseRepository; + this.reportDataService = reportDataService; + this.imageMessageMapper = imageMessageMapper; + this.iwpWebProperties = iwpWebProperties; + } + + @Secured({ "ROLE_CASE_SUPERVISOR", "ROLE_CASE_WORKER" }) + @PostMapping(value = "/api/iris/report") + public ResponseEntity generatePdfReport(@RequestBody ReportDataDTO reportData, Principal principal) { + UserAuthentication ua; + Gson gson = new Gson(); + + ReportData tempData; + + // Non-EBTS comparisons do not save automatically + if (reportData.getId() == null) { + ResponseEntity responseEntity = saveReport(reportData, principal); + // Check to see if save was successful + if (responseEntity.getStatusCode().is2xxSuccessful()) { + tempData = (ReportData) responseEntity.getBody(); + } else { + return responseEntity; + } + } else { + tempData = this.imageMessageMapper.toReportData(reportData); + } + + try { + ua = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + if (tempData == null) { + tempData = new ReportData(); + } + + try { + if (reportData.getCreatedBy() == null || reportData.getCreatedBy().getId() == null) + tempData.setCreatedBy(ua); + + tempData.setWorkedBy(ua); + + ReportData returnReport = reportService.createPdfReport(tempData, ua); + + return ResponseEntity.ok().body(returnReport); + } catch(Exception e){ + return ResponseEntity.badRequest().body(gson.toJson(e.getMessage())); + } + } + + @Secured({ "ROLE_CASE_SUPERVISOR", "ROLE_CASE_WORKER" }) + @GetMapping(value="/api/iris/report/download/{reportId}") + public ResponseEntity getPdfReport(@PathVariable("reportId") long reportId) { + + logger.info("/api/iris/report/download?id={}", reportId); + try{ + String filePath = this.findReportById(reportId); + try(FileInputStream fis = new FileInputStream(filePath)) { + + byte[] doc = new byte[fis.available()]; + if(fis.read(doc) <= 0){ + throw new IOException(String.format("Unable to read file %s", filePath)); + } + + Resource file = new ByteArrayResource(doc); + + if (doc.length == 0) { + return ResponseEntity.badRequest().body("Error Pulling Report Info"); + } + + return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, ATTACH_NAME_STRING + new File(filePath).getName() + "\"") + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(file); + } + + } catch (Exception docEx) { + logError(docEx); + return ResponseEntity.badRequest().body(docEx.getMessage()); + } + } + + @Secured({ "ROLE_CASE_SUPERVISOR", "ROLE_CASE_WORKER" }) + @PostMapping(value = "/api/iris/report/save") + public ResponseEntity saveReport(@RequestBody ReportDataDTO reportData, Principal principal) { + if (Boolean.TRUE.equals(reportData.getAllowNullRequester())) { + reportData.setIsFinished(true); + if (reportData.getCaseInformation().getRequestedBy().getId() == null){ + reportData.getCaseInformation().setRequestedBy(null); + } + } + + ReportData tempData = this.imageMessageMapper.toReportData(reportData); + + UserAuthentication ua; + + try { + ua = authService.getCurrentUser(principal); + return ResponseEntity.ok().body(this.saveReportData(tempData, ua)); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } catch (Exception e){ + return new ResponseEntity<>(e.getMessage(), HttpStatus.SEE_OTHER); + } + + } + + @Secured({ "ROLE_CASE_SUPERVISOR", "ROLE_CASE_WORKER" }) + @GetMapping(value = "/api/iris/report/find/{reportId}") + public ResponseEntity getReport(@PathVariable("reportId") long reportId) { + Optional reportOption = reportDataRepository.findById(reportId); + if (reportOption.isPresent()) { + return ResponseEntity.ok().body(reportOption.get()); + } + return ResponseEntity.notFound().build(); + } + + @Secured({ "ROLE_CASE_SUPERVISOR", "ROLE_CASE_WORKER" }) + @GetMapping(value = "/api/iris/report/count") + public ResponseEntity countReports(Principal principal) { + UserAuthentication ua; + try { + ua = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + return new ResponseEntity<>(this.reportDataRepository.countAllByCreatedByOrWorkedBy(ua, ua), HttpStatus.OK); + } + + @Secured({ "ROLE_CASE_SUPERVISOR", "ROLE_CASE_WORKER" }) + @GetMapping(value = "/api/iris/adjudication") + public ResponseEntity getAdjudicationKeys(Principal principal) { + return new ResponseEntity<>(this.iwpWebProperties.getAdjudicationKeys(), HttpStatus.OK); + } + + + @Secured({ "ROLE_CASE_SUPERVISOR", "ROLE_CASE_WORKER" }) + @GetMapping(value = "/api/iris/report/list") + public ResponseEntity listReports(@RequestParam(value = "start", defaultValue = "0") Integer start, + @RequestParam(value = "limit", defaultValue = "-1") Integer limit, + @RequestParam(value = "sort", defaultValue = "caseInformation.comparisonDate") String sortColumn, + @RequestParam(value = "direction", defaultValue = "desc") String direction, Principal principal) { + + Sort.Direction sortDirection = Sort.Direction.DESC; + if (direction.equalsIgnoreCase("asc")) { + sortDirection = Sort.Direction.ASC; + } + + if (limit == -1) { + limit = Integer.MAX_VALUE; + } + + UserAuthentication ua; + try { + ua = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + return new ResponseEntity<>(this.reportDataRepository.findByCreatedByOrWorkedBy(ua, ua, + PageRequest.of(start, limit, Sort.by(sortDirection, sortColumn))), HttpStatus.OK); + } + + @Secured({ "ROLE_CASE_SUPERVISOR", "ROLE_CASE_WORKER" }) + @GetMapping(value = "/api/iris/report/csv/download") + public ResponseEntity getReviewCsvReport(@RequestParam("id") String ids, Principal principal) { + + logger.info("/api/iris/report/download?id={}", ids); + UserAuthentication ua; + try { + ua = authService.getCurrentUser(principal); + } catch (UsernameNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN); + } + + String[] reviewIds = ids.split(","); + + List> outputList = new ArrayList<>(); + + String[] header = { "Filename", "FileType", "Finished", "Conducted By", "Comparison Date", "Probe UCN", + "Probe Notes" }; + + int maxCandidateCount = 0; + List headerList = new ArrayList<>(Arrays.asList(header)); + try { + + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + for (String stringId : reviewIds) { + long id = Long.parseLong(stringId); + ReportData rd = this.reportService.getReportDataById(id); + Probe probe = rd.getProbe(); + + List candidateList = rd.getCandidates(); + + if (maxCandidateCount < candidateList.size()) { + maxCandidateCount = candidateList.size(); + } + + CaseInformation caseInf = rd.getCaseInformation(); + if (probe != null && caseInf != null) { + List dataList = new ArrayList<>(); + + dataList.add(rd.getFileName()); + dataList.add(rd.getResponseType().toString()); + dataList.add(rd.getIsFinished().toString()); + dataList.add(rd.getWorkedBy().getProfile().getName()); + dataList.add(dateFormat.format(caseInf.getComparisonDate())); + dataList.add(probe.getSubjectIdentifier()); + dataList.add(StringUtils.join(pullImageNotes(probe.getImageList()))); + + if (candidateList != null) { + for (Candidate c : candidateList) { + dataList.add(c.getSubjectIdentifier()); + if (c.getAdjudicationResults() != null) { + dataList.add(c.getAdjudicationResults()); + } else { + dataList.add("No Decision Made"); + } + dataList.add(StringUtils.join(pullCandidateNotes(c))); + } + } + + outputList.add(dataList); + } else { + if (probe == null) { + logger.info("Probe is null"); + } + + if (caseInf == null) { + logger.info("Case Info is null"); + } + + if (candidateList.isEmpty()) { + logger.info("Candidate List is Empty"); + } + } + } + + ByteArrayOutputStream csvFile = new ByteArrayOutputStream(); + + OutputStreamWriter outputWriter = new OutputStreamWriter(csvFile); + CSVWriter writer = new CSVWriter(outputWriter); + + for (int i = 0; i < maxCandidateCount; i++) { + headerList.add(String.format("Candidate %d", i + 1)); + headerList.add(String.format("Decision %d", i + 1)); + headerList.add(String.format("Candidate Notes %d", i + 1)); + } + writer.writeNext(headerList.toArray(new String[0])); + + for (List line : outputList) { + writer.writeNext(line.toArray(new String[0])); + } + + writer.close(); + String fileName = String.format("%s.%s.csv", ua.getUsername(), + new SimpleDateFormat("yyyy-MM-dd hh-mm-ss").format(new Date())); + ByteArrayResource resourceFile = new ByteArrayResource(csvFile.toByteArray()); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, ATTACH_NAME_STRING + fileName + "\"") + .contentType(MediaType.APPLICATION_OCTET_STREAM).body(resourceFile); + } catch (IOException ioe) { + logError(ioe); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ioe.getMessage()); + } + } + + private List pullImageNotes(List imageData) { + List noteList = new ArrayList<>(); + + for (ImageData id : imageData) { + if (id.getImageNotes() != null) { + noteList.add(id.getImageNotes()); + } + } + + return noteList; + } + + private List pullCandidateNotes(Candidate candidate) { + List noteList = new ArrayList<>(); + if (candidate.getNote() != null) { + noteList.add(candidate.getNote()); + } + + noteList.addAll(pullImageNotes(candidate.getImageList())); + + return noteList; + } + + private void logError(Exception exc) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + exc.printStackTrace(pw); + logger.error("Error: {}", sw); + } + + private ReportData saveReportData(ReportData reportData, UserAuthentication ua) throws NoSuchAlgorithmException, InvalidMessageException { + // Check unrolled image id/bytes to see if they need stored + // JPA transitions modified how this needs handled + for(ImageData imageData : reportData.getProbe().getImageList()){ + this.checkSaveUnroll(imageData); + } + + for( Candidate candidate : reportData.getCandidates()){ + for(ImageData imageData : candidate.getImageList()){ + this.checkSaveUnroll(imageData); + } + } + + if (reportData.getCreatedBy() == null || reportData.getCreatedBy().getId() == null) + reportData.setCreatedBy(ua); + reportData.setWorkedBy(ua); + + reportData.getCaseInformation().setComparisonDate(Instant.now().toEpochMilli()); + + return reportDataService.saveReportData(reportData); + }// end of saveReport + + private void checkSaveUnroll(ImageData imageData) throws NoSuchAlgorithmException, InvalidMessageException { + if(imageData.getUnrollImageId() == null + && imageData.getUnrollImageData() != null){ + imageData.setUnrollImageId(this.irisService.uploadImage("image/png", imageData.getUnrollImageData())); + } + } + + private String findReportById(Long reportId) throws InvalidMessageException { + // Look for the report by Id + ReportData reportData = reportDataRepository.findById(reportId).orElse(null); + if (reportData == null) { + throw new InvalidMessageException("Report cannot be found under this id."); + } + + // Pull saved path from responseRepository + ReportResponse reportResponse = reportResponseRepository.findReportResponseByReportDataId(reportId); + + // Create the resulting PDF + logger.info("Id passed in is {}", reportId); + + return reportResponse.getFilePath(); + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/rest/ServicesController.java b/web/src/main/java/org/mitre/iwp/web/rest/ServicesController.java new file mode 100644 index 0000000..8b17b07 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/rest/ServicesController.java @@ -0,0 +1,781 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.rest; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import org.apache.http.client.HttpClient; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.mitre.iwp.web.IwpWebProperties; +import org.mitre.iwp.web.jms.ServiceMessageHandler; +import org.mitre.iwp.web.model.AciiResponse; +import org.mitre.iwp.web.model.BiqtResponse; +import org.mitre.iwp.web.model.ServiceCallStatus; +import org.mitre.iwp.web.model.ContactClassifierResponse; +import org.mitre.iwp.web.model.TshepiiResponse; +import org.mitre.iwp.web.model.TshepiiCompareResponse; +import org.mitre.iwp.web.model.IrisAnnotationResponse; +import org.mitre.iwp.web.model.PdmResponse; +import org.mitre.iwp.web.model.DualPDMResponse; +import org.mitre.iwp.web.model.DualPDMCompareResponse; +import org.mitre.iwp.web.model.VersionInfo; +import org.mitre.iwp.web.model.Polygon; +import org.mitre.iwp.web.model.PdmDeformedImageResponse; +import org.mitre.iwp.web.service.IrisService; +import org.mitre.iwp.web.service.EbtsImageRepository; +import org.mitre.iwp.web.service.PdmDeformedImageResponseRepository; +import org.mitre.iwp.web.service.AciiResponseRepository; +import org.mitre.iwp.web.service.BiqtResponseRepository; +import org.mitre.iwp.web.service.ContactClassifierResponseRepository; +import org.mitre.iwp.web.service.DualPDMResponseRepository; +import org.mitre.iwp.web.service.DualPDMCompareResponseRepository; +import org.mitre.iwp.web.service.TshepiiResponseRepository; +import org.mitre.iwp.web.service.TshepiiCompareResponseRepository; +import org.mitre.iwp.web.service.IrisAnnotationResponseRepository; +import org.mitre.iwp.web.service.PdmResponseRepository; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; +import java.util.Optional; +import java.util.ArrayList; + +@RestController +public class ServicesController { + private static final Logger log = LoggerFactory.getLogger(ServicesController.class); + private final IwpWebProperties iwpWebProperties; + private final AciiResponseRepository aciiResponseRepository; + private final BiqtResponseRepository biqtResponseRepository; + private final ContactClassifierResponseRepository contactClassifierResponseRepository; + private final TshepiiResponseRepository tshepiiResponseRepository; + private final DualPDMResponseRepository dualPDMResponseRepository; + private final TshepiiCompareResponseRepository tshepiiCompareResponseRepository; + private final DualPDMCompareResponseRepository dualPDMCompareResponseRepository; + private final IrisAnnotationResponseRepository irisAnnotationResponseRepository; + private final IrisService irisService; + private final ServiceMessageHandler serviceMessageHandler; + private final EbtsImageRepository ebtsImageRepository; + private final PdmResponseRepository pdmResponseRepository; + private final PdmDeformedImageResponseRepository pdmDeformedImageResponseRepository; + + private static final String FAILED_STRING = "Failed: "; + private static final String PNG_STRING = "image/png"; + private static final String RESUBMIT_STRING = "Resubmit flag: {}"; + private static final String ARTEMIS_QUEUE_KEY = "queue=\""; + + public ServicesController(IwpWebProperties iwpWebProperties, + AciiResponseRepository aciiResponseRepository, BiqtResponseRepository biqtResponseRepository, + ContactClassifierResponseRepository contactClassifierResponseRepository, + TshepiiResponseRepository tshepiiResponseRepository, + DualPDMResponseRepository dualPDMResponseRepository, + TshepiiCompareResponseRepository tshepiiCompareResponseRepository, + DualPDMCompareResponseRepository dualPDMCompareResponseRepository, + IrisAnnotationResponseRepository irisAnnotationResponseRepository, IrisService irisService, + ServiceMessageHandler serviceMessageHandler, + EbtsImageRepository ebtsImageRepository, + PdmResponseRepository pdmResponseRepository, + PdmDeformedImageResponseRepository pdmDeformedImageResponseRepository) { + this.iwpWebProperties = iwpWebProperties; + this.aciiResponseRepository = aciiResponseRepository; + this.biqtResponseRepository = biqtResponseRepository; + this.contactClassifierResponseRepository = contactClassifierResponseRepository; + this.tshepiiResponseRepository = tshepiiResponseRepository; + this.dualPDMResponseRepository = dualPDMResponseRepository; + this.tshepiiCompareResponseRepository = tshepiiCompareResponseRepository; + this.dualPDMCompareResponseRepository = dualPDMCompareResponseRepository; + this.irisAnnotationResponseRepository = irisAnnotationResponseRepository; + this.irisService = irisService; + this.serviceMessageHandler = serviceMessageHandler; + this.ebtsImageRepository = ebtsImageRepository; + this.pdmResponseRepository = pdmResponseRepository; + this.pdmDeformedImageResponseRepository = pdmDeformedImageResponseRepository; + } + + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @GetMapping(value="/api/service/status") + public ResponseEntity getServiceStatus(){ + Gson gson = new Gson(); + return ResponseEntity.ok().body(gson.toJson(this.serviceMessageHandler.getServicesStatus())); + } + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/activeMQ/services/status") + public ResponseEntity getActiveMqServicesStatus() throws IOException { + try { + HttpGet get = new HttpGet(iwpWebProperties.getActiveMqHttpAddress()); + String activeMqAuth = iwpWebProperties.getActiveMqUserName() + ":" + iwpWebProperties.getActiveMqPassword(); + get.setHeader("Authorization", "Basic " + + Base64.getEncoder().encodeToString(activeMqAuth.getBytes(StandardCharsets.UTF_8))); + CredentialsProvider provider = new BasicCredentialsProvider(); + UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(iwpWebProperties.getActiveMqUserName(), + iwpWebProperties.getActiveMqPassword()); + provider.setCredentials(AuthScope.ANY, credentials); + HttpClient client = HttpClientBuilder.create().setDefaultCredentialsProvider(provider).build(); + HttpResponse response = client.execute(get); + + String responseBody = EntityUtils.toString(response.getEntity()); + JSONArray array = new JSONArray(); + + if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) { + log.info("ActiveMQ status request failed: {}", responseBody); + return ResponseEntity.ok().body(array.toString()); + } + + JSONObject responseJson = new JSONObject(responseBody); + JSONObject queues = responseJson.optJSONObject("value"); + + if (queues == null) { + log.info("ActiveMQ status response did not contain a value object"); + return ResponseEntity.ok().body(array.toString()); + } + + int index = 0; + for (String mbeanName : queues.keySet()) { + JSONObject queueStats = queues.optJSONObject(mbeanName); + String name = getArtemisQueueName(mbeanName); + + if (queueStats == null || name == null || !name.startsWith("iris.")) { + continue; + } + + JSONObject queue = new JSONObject(); + queue.put("name", name); + queue.put("consumerCount", String.valueOf(queueStats.optInt("ConsumerCount", 0))); + queue.put("enqueueCount", String.valueOf(queueStats.optLong("MessagesAdded", 0))); + queue.put("dequeueCount", String.valueOf(queueStats.optLong("MessagesAcknowledged", 0))); + array.put(index++, queue); + } + + return ResponseEntity.ok().body(array.toString()); + } catch (IOException | JSONException e) { + log.info(e.getMessage()); + } + return null; + } + + private static String getArtemisQueueName(String mbeanName) { + int queueStart = mbeanName.indexOf(ARTEMIS_QUEUE_KEY); + + if (queueStart == -1) { + return null; + } + + int nameStart = queueStart + ARTEMIS_QUEUE_KEY.length(); + int nameEnd = mbeanName.indexOf("\"", nameStart); + + if (nameEnd == -1) { + return null; + } + + return mbeanName.substring(nameStart, nameEnd); + } + + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @GetMapping(value="/api/service/versions") + public ResponseEntity getVersionInfo() { + VersionInfo info = new VersionInfo(); + info.setIwpVersion(this.iwpWebProperties.getIwpBackendVersion()); + info.setUiVersion(this.iwpWebProperties.getIwpFrontendVersion()); + + info.getServiceVersions().put("biqt", this.iwpWebProperties.getBiqtServiceVersion()); + info.getServiceVersions().put("acii", this.iwpWebProperties.getAciiServiceVersion()); + info.getServiceVersions().put("tshepii", this.iwpWebProperties.getTshepiiServiceVersion()); + info.getServiceVersions().put("annotation", this.iwpWebProperties.getIrisAnnotationServiceVersion()); + info.getServiceVersions().put("pdm", this.iwpWebProperties.getPdmServiceVersion()); + + Gson gson = new Gson(); + return ResponseEntity.ok().body(gson.toJson(info)); + } + + // ACII + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @PostMapping(value={"/api/service/acii/snapshot/{ebtsImageId}"}) + public ResponseEntity requestAciiSnapshot(@PathVariable("ebtsImageId") Long ebtsImageId, @RequestBody String imageData){ + log.info("Requesting ACII Snapshot"); + Long snapshotImageId = -1l; + + try{ + byte[] imageBytes = Base64.getDecoder().decode(imageData); + + snapshotImageId = this.irisService.uploadImage(PNG_STRING, imageBytes, "", false); + + } catch (Exception e){ + log.info(e.getMessage()); + log.error(FAILED_STRING, e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(-1l); + } + + Long responseId = this.serviceMessageHandler.processAcii(this.ebtsImageRepository.getReferenceById(snapshotImageId), ebtsImageId); + + return ResponseEntity.ok().body(responseId); + } + + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @GetMapping(value="/api/service/acii/status/{ebtsImageId}/{modifiedImageId}") + public ResponseEntity getAciiResponseStatus(@PathVariable("ebtsImageId") Long ebtsImageId, @PathVariable("modifiedImageId") Long modifiedImageId) { + Long queryImageId = ebtsImageId; + if (modifiedImageId != -1) { + queryImageId = modifiedImageId; + } + + AciiResponse response = this.aciiResponseRepository.findByImageId(queryImageId); + + if (response == null) { + return ResponseEntity.ok(ServiceCallStatus.NotSent); + } + + this.aciiResponseRepository.save(response); + + return ResponseEntity.ok().body(response.getStatus()); + } + + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @GetMapping(value={"/api/service/acii/{imageId}", "/api/service/acii/{imageId}/{resubmit}"}) + public ResponseEntity getAciiResponse(@PathVariable("imageId") Long imageId, + @PathVariable("resubmit") Optional resubmitFlag) { + AciiResponse response = this.aciiResponseRepository.findByImageId(imageId); + AciiResponse returnResponse = response; + + log.debug(RESUBMIT_STRING, resubmitFlag); + + if (resubmitFlag.isPresent() && Boolean.TRUE.equals(resubmitFlag.get())) { + this.aciiResponseRepository.save(response); + } + + return ResponseEntity.ok().body(returnResponse); + } + + // BIQT & Contact + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @PostMapping(value={"/api/service/biqtContact/snapshot/{ebtsImageId}"}) + public ResponseEntity requestBiqtContactSnapshot(@PathVariable("ebtsImageId") Long ebtsImageId, @RequestBody String imageData){ + log.info("Requesting BIQT and Contact Snapshot"); + Long snapshotImageId = -1l; + + try{ + byte[] imageBytes = Base64.getDecoder().decode(imageData); + + snapshotImageId = this.irisService.uploadImage(PNG_STRING, imageBytes, "", false); + + } catch (Exception e){ + log.info(e.getMessage()); + log.error(FAILED_STRING, e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(-1l); + } + + Long responseId = this.serviceMessageHandler.processBiqtContact(this.ebtsImageRepository.getReferenceById(snapshotImageId), ebtsImageId); + + return ResponseEntity.ok().body(responseId); + } + + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @GetMapping(value="/api/service/biqtContact/status/{ebtsImageId}/{modifiedImageId}") + public ResponseEntity getBiqtContactResponseStatus(@PathVariable("ebtsImageId") Long ebtsImageId, @PathVariable("modifiedImageId") Long modifiedImageId){ + ServiceCallStatus biqtStatus = this.getBiqtResponseStatus(ebtsImageId, modifiedImageId); + ServiceCallStatus contactStatus = this.getContactResponseStatus(ebtsImageId, modifiedImageId); + + ServiceCallStatus[] response = {biqtStatus, contactStatus}; + return ResponseEntity.ok().body(response); + } + + private ServiceCallStatus getBiqtResponseStatus(Long ebtsImageId, Long modifiedImageId){ + Long queryImageId = ebtsImageId; + if (modifiedImageId != -1) { + queryImageId = modifiedImageId; + } + + BiqtResponse response = this.biqtResponseRepository.findByImageId(queryImageId); + + if (response == null) { + return ServiceCallStatus.NotSent; + } + + this.biqtResponseRepository.save(response); + + return response.getStatus(); + } + + private ServiceCallStatus getContactResponseStatus(Long ebtsImageId, Long modifiedImageId) { + Long queryImageId = ebtsImageId; + if (modifiedImageId != -1) { + queryImageId = modifiedImageId; + } + + ContactClassifierResponse response = this.contactClassifierResponseRepository.findByImageId(queryImageId); + + if (response == null) { + return ServiceCallStatus.NotSent; + } + + this.contactClassifierResponseRepository.save(response); + + return response.getStatus(); + } + + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @GetMapping(value={"/api/service/biqtContact/biqt/{imageId}","/api/service/biqtContact/biqt/{imageId}/{resubmit}"}) + public ResponseEntity getBiqtResponse(@PathVariable("imageId") Long imageId, @PathVariable("resubmit") Optional resubmitFlag){ + BiqtResponse response = this.biqtResponseRepository.findByImageId(imageId); + + log.debug(RESUBMIT_STRING, resubmitFlag); + + if (resubmitFlag.isPresent() && Boolean.TRUE.equals(resubmitFlag.get())) { + this.biqtResponseRepository.save(response); + } + + return ResponseEntity.ok().body(response); + } + + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @GetMapping(value={"/api/service/biqtContact/contact/{imageId}","/api/service/biqtContact/contact/{imageId}/{resubmit}"}) + public ResponseEntity getContactResponse(@PathVariable("imageId") Long imageId, @PathVariable("resubmit") Optional resubmitFlag){ + ContactClassifierResponse response = this.contactClassifierResponseRepository.findByImageId(imageId); + if (resubmitFlag.isPresent() && Boolean.TRUE.equals(resubmitFlag.get())) { + this.contactClassifierResponseRepository.save(response); + } + return ResponseEntity.ok().body(response); + } + + // Iris Annotation + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @PostMapping(value={"/api/service/annotation/snapshot/{ebtsImageId}"}) + public ResponseEntity requestIrisAnnotationSnapshot(@PathVariable("ebtsImageId") Long ebtsImageId, @RequestBody String imageData){ + log.info("Requesting Annotation Snapshot"); + Long snapshotImageId = -1l; + + try{ + byte[] imageBytes = Base64.getDecoder().decode(imageData); + + snapshotImageId = this.irisService.uploadImage(PNG_STRING, imageBytes, "", false); + + } catch (Exception e){ + log.info(e.getMessage()); + log.error(FAILED_STRING, e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(-1l); + } + + Long responseId = this.serviceMessageHandler.processIrisAnnotation(this.ebtsImageRepository.getReferenceById(snapshotImageId), ebtsImageId); + + return ResponseEntity.ok().body(responseId); + } + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/service/annotation/status/{ebtsImageId}/{modifiedImageId}") + public ResponseEntity getIrisAnnotationResponseStatus(@PathVariable("ebtsImageId") Long ebtsImageId, @PathVariable("modifiedImageId") Long modifiedImageId) { + Long queryImageId = ebtsImageId; + if (modifiedImageId != -1) { + queryImageId = modifiedImageId; + } + + IrisAnnotationResponse response = this.irisAnnotationResponseRepository.findByImageId(queryImageId); + + if (response == null) { + return ResponseEntity.ok(ServiceCallStatus.NotSent); + } + + this.irisAnnotationResponseRepository.save(response); + + return ResponseEntity.ok(response.getStatus()); + } + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = {"/api/service/annotation/{imageId}", "/api/service/annotation/{imageId}/{resubmit}"}) + public ResponseEntity getIrisAnnotationResponse(@PathVariable("imageId") Long imageId, + @PathVariable("resubmit") Optional resubmitFlag) { + + IrisAnnotationResponse response = this.irisAnnotationResponseRepository.findByImageId(imageId); + IrisAnnotationResponse returnResponse = response; + + log.debug(RESUBMIT_STRING, resubmitFlag); + + if (resubmitFlag.isPresent() && Boolean.TRUE.equals(resubmitFlag.get())) { + this.irisAnnotationResponseRepository.save(response); + } + + if (returnResponse.getStatus() == ServiceCallStatus.Received) { + returnResponse.buildIrisAnnotationReply(); + } + + for (Polygon poly : returnResponse.getPolygonList()) { + if (this.iwpWebProperties.getAnnotations().containsKey(poly.code)) { + poly.setName(this.iwpWebProperties.getAnnotations().get(poly.code)); + poly.setColor(this.iwpWebProperties.getAnnotationsColorHex().get(poly.code)); + } + } + + Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); + + return ResponseEntity.ok(gson.toJson(returnResponse)); + } + + // Tshepii + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @PostMapping(value="/api/service/tshepii/snapshot/{probeEbtsImageId}/{candidateEbtsImageId}") + public ResponseEntity> requestTshepiiSnapshot(@PathVariable("probeEbtsImageId") Long probeEbtsImageId, @PathVariable("candidateEbtsImageId") Long candidateEbtsImageId, @RequestBody List imageData){ + log.info("Requesting Tshepii Snapshot"); + Long probeSnapshotImageId = -1l; + Long candidateSnapshotImageId = -1l; + + // Creating and setting the return array as a list of -1 + List returnArray = new ArrayList<>(); + returnArray.add(-1l); + returnArray.add(-1l); + + String probeImageData = imageData.get(0); + String candidateImageData = imageData.get(1); + + try{ + byte[] probeImageBytes = Base64.getDecoder().decode(probeImageData); + byte[] candidateImageBytes = Base64.getDecoder().decode(candidateImageData); + + probeSnapshotImageId = this.irisService.uploadImage(PNG_STRING, probeImageBytes, "", false); + candidateSnapshotImageId = this.irisService.uploadImage(PNG_STRING, candidateImageBytes, "", false); + + } catch (Exception e){ + log.info(e.getMessage()); + log.error(FAILED_STRING, e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(returnArray); + } + + Long probeResponseId = this.serviceMessageHandler.processTshepii(this.ebtsImageRepository.getReferenceById(probeSnapshotImageId), probeEbtsImageId); + Long candidateResponseId = this.serviceMessageHandler.processTshepii(this.ebtsImageRepository.getReferenceById(candidateSnapshotImageId), candidateEbtsImageId); + + returnArray.set(0, probeResponseId); + returnArray.set(1, candidateResponseId); + + return ResponseEntity.ok().body(returnArray); + } + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/service/tshepii/status/{probeEbtsImageId}/{candidateEbtsImageId}/{probeModifiedImageId}/{candidateModifiedImageId}") + public ResponseEntity getTshepiiResponseStatus(@PathVariable("probeEbtsImageId") Long probeEbtsImageId, @PathVariable("candidateEbtsImageId") Long candidateEbtsImageId, @PathVariable("probeModifiedImageId") Long probeModifiedImageId, @PathVariable("candidateModifiedImageId") Long candidateModifiedImageId) { + + Long queryProbeImageId = probeEbtsImageId; + if (probeModifiedImageId != -1) { + queryProbeImageId = probeModifiedImageId; + } + + Long queryCandidateImageId = candidateEbtsImageId; + if (candidateModifiedImageId != -1) { + queryCandidateImageId = candidateModifiedImageId; + } + + TshepiiResponse responseProbe = this.tshepiiResponseRepository.findByImageId(queryProbeImageId); + TshepiiResponse responseCand = this.tshepiiResponseRepository.findByImageId(queryCandidateImageId); + + if (responseProbe == null || responseCand == null) { + return ResponseEntity.ok(ServiceCallStatus.NotSent); + } + + responseProbe.setEbtsImageId(probeEbtsImageId); + this.tshepiiResponseRepository.save(responseProbe); + + responseCand.setEbtsImageId(candidateEbtsImageId); + this.tshepiiResponseRepository.save(responseCand); + + if (responseProbe.getStatus() == ServiceCallStatus.Received + && responseCand.getStatus() == ServiceCallStatus.Received) { + return ResponseEntity.ok().body(ServiceCallStatus.Received); + } + + if (responseProbe.getStatus() == ServiceCallStatus.Error || responseCand.getStatus() == ServiceCallStatus.Error) { + return ResponseEntity.ok().body(ServiceCallStatus.Error); + } + + if (responseProbe.getStatus() == ServiceCallStatus.NotSent + || responseCand.getStatus() == ServiceCallStatus.NotSent) { + return ResponseEntity.ok().body(ServiceCallStatus.NotSent); + } + + return ResponseEntity.ok().body(ServiceCallStatus.Pending); + } + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/service/tshepii/compare/status/{probeEbtsImageId}/{candidateEbtsImageId}/{probeModifiedImageId}/{candidateModifiedImageId}") + public ResponseEntity getTshepiiCompareResponseStatus(@PathVariable("probeEbtsImageId") Long probeEbtsImageId, @PathVariable("candidateEbtsImageId") Long candidateEbtsImageId, @PathVariable("probeModifiedImageId") Long probeModifiedImageId, @PathVariable("candidateModifiedImageId") Long candidateModifiedImageId) { + + Long queryProbeImageId = probeEbtsImageId; + if (probeModifiedImageId != -1) { + queryProbeImageId = probeModifiedImageId; + } + + Long queryCandidateImageId = candidateEbtsImageId; + if (candidateModifiedImageId != -1) { + queryCandidateImageId = candidateModifiedImageId; + } + + TshepiiCompareResponse response = this.tshepiiCompareResponseRepository.findByImageIdAndCandidateImageId(queryProbeImageId, queryCandidateImageId); + + if (response == null) { + return ResponseEntity.ok().body(ServiceCallStatus.NotSent); + } + + response.setEbtsImageId(probeEbtsImageId); + response.setCandidateEbtsImageId(candidateEbtsImageId); + this.tshepiiCompareResponseRepository.save(response); + + return ResponseEntity.ok(response.getStatus()); + } + + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @GetMapping(value={"/api/service/tshepii/compare/{probeId}/{candidateId}", "/api/service/tshepii/compare/{probeId}/{candidateId}/{resubmit}"}) + public ResponseEntity getTshepiiCompareResponse(@PathVariable("probeId") Long probeId, @PathVariable("candidateId") Long candidateId, + @PathVariable("resubmit") Optional resubmitFlag) { + + log.info("ComparisonResponse Request Received for Probe: {} and Candidate: {}", probeId, candidateId); + TshepiiCompareResponse response = this.tshepiiCompareResponseRepository.findByImageIdAndCandidateImageId(probeId, candidateId); + + if(response == null) { + this.irisService.createTshepiiCompareRequest(probeId, candidateId); + response = new TshepiiCompareResponse(); + // // Can info - this is not the actual object stored in the DB + // // May need to change if we screw something up in communication between UI and backend + response.setStatus(ServiceCallStatus.Pending); + response.setImageId(probeId); + response.setCandidateImageId(candidateId); + } + + if(response.getStatus() == ServiceCallStatus.Received){ + boolean responseBuilt = response.buildFromServiceResponse(); + if(!responseBuilt){ + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error generating Compare Response"); + } + } + + Gson gson = new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .create(); + return ResponseEntity.ok(gson.toJson(response)); + } + + // DUAL PDM + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/service/dualpdm/status/{probeEbtsImageId}/{candidateEbtsImageId}/{probeModifiedImageId}/{candidateModifiedImageId}") + public ResponseEntity getDualPDMResponseStatus(@PathVariable("probeEbtsImageId") Long probeEbtsImageId, @PathVariable("candidateEbtsImageId") Long candidateEbtsImageId, @PathVariable("probeModifiedImageId") Long probeModifiedImageId, @PathVariable("candidateModifiedImageId") Long candidateModifiedImageId) { + + Long queryProbeImageId = probeEbtsImageId; + if (probeModifiedImageId != -1) { + queryProbeImageId = probeModifiedImageId; + } + + Long queryCandidateImageId = candidateEbtsImageId; + if (candidateModifiedImageId != -1) { + queryCandidateImageId = candidateModifiedImageId; + } + + DualPDMResponse responseProbe = this.dualPDMResponseRepository.findByImageId(queryProbeImageId); + DualPDMResponse responseCandidate = this.dualPDMResponseRepository.findByImageId(queryCandidateImageId); + + // Probe AND/OR Candidate have not been saved yet + if (responseProbe == null || responseCandidate == null) { + log.info("Probe AND/OR Candidate have not been saved yet"); + return ResponseEntity.ok(ServiceCallStatus.NotSent); + } + + responseProbe.setEbtsImageId(probeEbtsImageId); + this.dualPDMResponseRepository.save(responseProbe); + + responseCandidate.setEbtsImageId(candidateEbtsImageId); + this.dualPDMResponseRepository.save(responseCandidate); + + // Probe AND Candidate have been received + if (responseProbe.getStatus() == ServiceCallStatus.Received && responseCandidate.getStatus() == ServiceCallStatus.Received) { + log.info("Both Probe AND Candidate have been received individually and sending the request for the comparison."); + if (this.dualPDMCompareResponseRepository.findByProbeIdAndCandidateId(queryProbeImageId, queryCandidateImageId) == null) { + this.serviceMessageHandler.requestDualPDMComparison(queryProbeImageId, queryCandidateImageId); // Create a new request + } + return ResponseEntity.ok().body(ServiceCallStatus.Received); + } + + // Probe AND/OR Candidate have caused an error + if (responseProbe.getStatus() == ServiceCallStatus.Error || responseCandidate.getStatus() == ServiceCallStatus.Error) { + log.info("Probe AND/OR Candidate have caused an error"); + return ResponseEntity.ok().body(ServiceCallStatus.Error); + } + + // Not NULL but Probe AND/OR Candidate have not been sent + if (responseProbe.getStatus() == ServiceCallStatus.NotSent || responseCandidate.getStatus() == ServiceCallStatus.NotSent) { + log.info("NOT NULL but Probe//Candidate are NOT SENT"); + return ResponseEntity.ok().body(ServiceCallStatus.NotSent); + } + + return ResponseEntity.ok().body(ServiceCallStatus.Pending); + } + + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/service/dualpdm/compare/status/{probeEbtsImageId}/{candidateEbtsImageId}/{probeModifiedImageId}/{candidateModifiedImageId}") + public ResponseEntity getDualPDMCompareResponseStatus(@PathVariable("probeEbtsImageId") Long probeEbtsImageId, @PathVariable("candidateEbtsImageId") Long candidateEbtsImageId, @PathVariable("probeModifiedImageId") Long probeModifiedImageId, @PathVariable("candidateModifiedImageId") Long candidateModifiedImageId) { + Long queryProbeImageId = probeEbtsImageId; + if (probeModifiedImageId != -1) { + queryProbeImageId = probeModifiedImageId; + } + + Long queryCandidateImageId = candidateEbtsImageId; + if (candidateModifiedImageId != -1) { + queryCandidateImageId = candidateModifiedImageId; + } + + DualPDMCompareResponse response = this.dualPDMCompareResponseRepository.findByProbeIdAndCandidateId(queryProbeImageId, queryCandidateImageId); + + if (response == null) { + return ResponseEntity.ok().body(ServiceCallStatus.NotSent); + } + + response.setEbtsImageId(probeEbtsImageId); + response.setCandidateEbtsImageId(candidateEbtsImageId); + this.dualPDMCompareResponseRepository.save(response); + + return ResponseEntity.ok(response.getStatus()); + } + + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @GetMapping(value={"/api/service/dualpdm/compare/{probeId}/{candidateId}", "/api/service/dualpdm/compare/{probeId}/{candidateId}/{resubmit}"}) + public ResponseEntity getDualPDMCompareResponse(@PathVariable("probeId") Long probeId, @PathVariable("candidateId") Long candidateId, + @PathVariable("resubmit") Optional resubmitFlag) { + + log.info("Dual PDM ComparisonResponse Request Received for Probe: {} and Candidate: {}", probeId, candidateId); + + DualPDMCompareResponse response = this.dualPDMCompareResponseRepository.findByProbeIdAndCandidateId(probeId, candidateId); + + // Handle the case where no response exists in the database + if (response == null) { + log.info("No existing Dual PDM comparison response found. Creating a new request."); + // Create a new request and send JMS Message + this.serviceMessageHandler.requestDualPDMComparison(probeId, candidateId); + response = new DualPDMCompareResponse(); + response.setStatus(ServiceCallStatus.Pending); + response.setProbeId(probeId); + response.setCandidateId(candidateId); + } + + // If the response status is "Received", process the response further + if (response.getStatus() == ServiceCallStatus.Received) { + boolean responseBuilt = response.buildFromServiceResponse(); // Build the response from the service data + if (!responseBuilt) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Dual PDM Error generating Compare Response"); + } + } + + // Serialize the response to JSON and return it + Gson gson = new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .create(); + return ResponseEntity.ok(gson.toJson(response)); + } + + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @GetMapping(value="/api/service/dualpdm/response/{imageId}") + public ResponseEntity getDualPDMResponseImage(@PathVariable("imageId") String imageId) { + long imgId = Long.parseLong(imageId); + DualPDMResponse response = this.dualPDMResponseRepository.findByImageId(imgId); + + + log.info("DualPDM Response Image Request received for imageId {} of size {}", response.getImageId(), response.getByteSize()); + + Gson gson = new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .create(); + return ResponseEntity.ok(gson.toJson(response)); + } + + // PDM + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @PostMapping(value={"/api/service/pdm/snapshot/{ebtsImageId}"}) + public ResponseEntity requestPdmSnapshot(@PathVariable("ebtsImageId") Long ebtsImageId, @RequestBody String imageData){ + log.info("Requesting PDM Snapshot"); + Long snapshotImageId = -1l; + + try{ + byte[] imageBytes = Base64.getDecoder().decode(imageData); + + snapshotImageId = this.irisService.uploadImage(PNG_STRING, imageBytes, "", false); + log.info("Requesting PDM Snapshot id {}", snapshotImageId); + + + } catch (Exception e){ + log.info(e.getMessage()); + log.error(FAILED_STRING, e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(-1l); + } + + Long responseId = this.serviceMessageHandler.processPdm(this.ebtsImageRepository.getReferenceById(snapshotImageId), ebtsImageId); + + return ResponseEntity.ok().body(responseId); + } + + @Secured({ "ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR" }) + @GetMapping(value = "/api/service/pdm/status/{ebtsImageId}/{modifiedImageId}") + public ResponseEntity getPdmResponseStatus(@PathVariable("ebtsImageId") Long ebtsImageId, @PathVariable("modifiedImageId") Long modifiedImageId) { + Long queryImageId = ebtsImageId; + if (modifiedImageId != -1) { + queryImageId = modifiedImageId; + } + + PdmResponse response = this.pdmResponseRepository.findByImageId(queryImageId); + + + if (response == null) { + return ResponseEntity.ok(ServiceCallStatus.NotSent); + } + + this.pdmResponseRepository.save(response); + + if (response.getStatus() == ServiceCallStatus.Received) { + return ResponseEntity.ok().body(ServiceCallStatus.Received); + } + + if (response.getStatus() == ServiceCallStatus.Error) { + return ResponseEntity.ok().body(ServiceCallStatus.Error); + } + + if (response.getStatus() == ServiceCallStatus.NotSent) { + return ResponseEntity.ok().body(ServiceCallStatus.NotSent); + } + + return ResponseEntity.ok().body(ServiceCallStatus.Pending); + } + + @Secured({"ROLE_CASE_WORKER", "ROLE_CASE_SUPERVISOR"}) + @GetMapping(value="/api/service/pdm/response/{deformedId}") + public ResponseEntity getPdmResponse(@PathVariable("deformedId") String deformedId) { + PdmDeformedImageResponse response = this.pdmDeformedImageResponseRepository.findByDeformedId(deformedId); + + log.info("PdmResponse Request received for deformedId: {}", response.getDeformedId()); + + Gson gson = new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .create(); + return ResponseEntity.ok(gson.toJson(response)); + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/security/DisabledWebSecurityConfig.java b/web/src/main/java/org/mitre/iwp/web/security/DisabledWebSecurityConfig.java new file mode 100644 index 0000000..c5f8185 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/security/DisabledWebSecurityConfig.java @@ -0,0 +1,91 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.security; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; + +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; +import org.springframework.web.filter.OncePerRequestFilter; + + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Configuration +@ConditionalOnProperty(name = "iwp.security.secured", havingValue = "false") +public class DisabledWebSecurityConfig { + + private final IwpUserDetailsService userDetailsService; + + CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); + CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler(); + + public DisabledWebSecurityConfig(@Lazy IwpUserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + http.csrf(csrf -> csrf + .csrfTokenRepository(tokenRepository) + .csrfTokenRequestHandler(requestHandler) + ) + .addFilterAfter(new CsrfCookieFilterForDisabled(), BasicAuthenticationFilter.class); + + // If you have issues with 403 errors on POST requests you may have CSRF issues. + // Be sure you are sending in the CSRF token header in with your request and with + // the correct header name (X-CSRF-TOKEN). You can use http.csrf(AbstractHttpConfigurer::disable) + // for troubleshooting but should not leave it disabled. + + return http.build(); + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); + } + +} + +final class CsrfCookieFilterForDisabled extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); + csrfToken.getToken(); + + filterChain.doFilter(request, response); + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/security/IwpAuthService.java b/web/src/main/java/org/mitre/iwp/web/security/IwpAuthService.java new file mode 100644 index 0000000..cb02076 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/security/IwpAuthService.java @@ -0,0 +1,88 @@ + +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.security; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.security.Principal; + +import org.mitre.iwp.web.model.UserAuthentication; +import org.mitre.iwp.web.service.UserAuthenticationRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.dao.DataAccessException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +public class IwpAuthService { + + private static final Logger logger = LoggerFactory.getLogger(IwpAuthService.class); + + private UserAuthenticationRepository uaRepository; + + // Booleans to override responses if needed + private final boolean isSecure; + + @Value("${iwp.users.defaultAdminUsername}") + private String defaultAdminUsername; + + public IwpAuthService(UserAuthenticationRepository uaRepository, @Value("${iwp.security.secured}") boolean isSecure) { + this.uaRepository = uaRepository; + this.isSecure = isSecure; + } + + public UserAuthentication getUserById(Long id){ + return uaRepository.findById(id).orElse(null); + } + + /** + * Finds the UserAuthentication for current user + * + * @param principal + * @return + * @throws UsernameNotFoundException + */ + public UserAuthentication getCurrentUser(Principal principal) throws UsernameNotFoundException { + + // Default the returned user to default admin if we're not using logon + String principalName = (principal == null) ? null : principal.getName(); + String username = (!isSecure) ? defaultAdminUsername : principalName; + + logger.debug("Principal: {}", username); + + try { + return uaRepository.findByUsername(username); + }catch (NullPointerException | DataAccessException e){ + logger.error(e.getMessage()); + } + + throw new BadCredentialsException("Bad Credentials"); + } + + /** + * Determines what username to return depending on environment variables + * + * NOTE: Ideally this isn't actually used in the future as even in testing there + * should be authentication via a token even when not used with a web GUI. For + * now we will by pass on our own. + * + * @param principal + * @return principal username or testing username + */ + public String getCurrentUsername(Principal principal) { + String principalName = (principal == null) ? "" : principal.getName(); + return (!isSecure) ? defaultAdminUsername : principalName; + } + +} diff --git a/web/src/main/java/org/mitre/iwp/web/security/IwpHttpConfig.java b/web/src/main/java/org/mitre/iwp/web/security/IwpHttpConfig.java new file mode 100644 index 0000000..104235a --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/security/IwpHttpConfig.java @@ -0,0 +1,29 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.security; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class IwpHttpConfig { + private boolean isSecure; + + public IwpHttpConfig(@Value("${iwp.security.secured}") boolean isSecure) { + this.isSecure = isSecure; + } + + public boolean getIsSecure() { + return isSecure; + } +} + + diff --git a/web/src/main/java/org/mitre/iwp/web/security/IwpUserDetailsService.java b/web/src/main/java/org/mitre/iwp/web/security/IwpUserDetailsService.java new file mode 100644 index 0000000..727bfef --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/security/IwpUserDetailsService.java @@ -0,0 +1,125 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.security; + +import org.mitre.iwp.web.dto.ProfileDTO; +import org.mitre.iwp.web.dto.UserAuthenticationDTO; +import org.mitre.iwp.web.exception.InvalidMessageException; +import org.mitre.iwp.web.model.Profile; +import org.mitre.iwp.web.model.UserAuthentication; +import org.mitre.iwp.web.model.UserSetting; +import org.mitre.iwp.web.service.UserAuthenticationRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component(value="iwpUserDetailsService") +public class IwpUserDetailsService implements UserDetailsService { + private static final Logger logger = LoggerFactory.getLogger(IwpUserDetailsService.class); + + private final UserAuthenticationRepository userAuthenticationRepository; + + private final PasswordEncoder passwordEncoder; + + public IwpUserDetailsService(UserAuthenticationRepository userAuthenticationRepository, + PasswordEncoder passwordEncoder) { + this.userAuthenticationRepository = userAuthenticationRepository; + this.passwordEncoder = passwordEncoder; + } + + public UserDetails loadUserByUsername(String userId) + throws UsernameNotFoundException { + + UserAuthentication user = this.userAuthenticationRepository.findByUsername(userId); + if( user == null) { + logger.error("user not found: {}", userId); + throw new UsernameNotFoundException("Invalid username or password"); + } + + List roles = new ArrayList<>(); + for( String role : user.getRoles() ) { + roles.add(role.replace("ROLE_", "")); + } + + UserDetails ret = User.withUsername(user.getUsername()) + .password(user.getPassword()) + .roles(roles.toArray(new String[0])) + .build(); + + logger.info("user found returning user"); + return ret; + } + + public void addUser(UserAuthenticationDTO userDto, String username) throws InvalidMessageException { + UserAuthentication ret = this.userAuthenticationRepository.findByUsername(username); + + if( ret != null ) { + throw new InvalidMessageException("User already exists"); + } + + UserAuthentication userObject = convertUserDtoToObject(userDto); + + userAuthenticationRepository.save(userObject); + + logger.info("Adding user with name: {}", userObject.getUsername()); + } + + public void deleteUser(long userId) throws InvalidMessageException { + if(this.userAuthenticationRepository.existsById(userId)) { + this.userAuthenticationRepository.deleteById(userId); + } + else { + throw new InvalidMessageException("User does not exist"); + } + } + + public void updatePassword(String userName, String password) throws InvalidMessageException { + UserAuthentication user = this.userAuthenticationRepository.findByUsername(userName); + + if(user == null){ + throw new InvalidMessageException("User does not exist"); + } else { + logger.info("Ua Found, Name {}", user.getId()); + user.setPassword(password); + this.userAuthenticationRepository.save(user); + } + } + + private UserAuthentication convertUserDtoToObject(UserAuthenticationDTO userDto) { + UserAuthentication userObject = new UserAuthentication(); + userObject.setUsername(userDto.getUsername()); + userObject.setRoles(userDto.getRoles()); + + ProfileDTO profileDto = userDto.getProfile(); + // If the profile name wasn't set for that user, we set it to the user's username. + String profileName = profileDto.getName().isEmpty() ? userDto.getUsername(): profileDto.getName(); + + UserSetting userSetting = new UserSetting(profileDto.getUserSetting().getId(), profileDto.getUserSetting().getStrokeWidth(), profileDto.getUserSetting().getDrawColor(), profileDto.getUserSetting().getFillColor(), profileDto.getUserSetting().getNumHistory(), profileDto.getUserSetting().getTshepiiStrokeOpacity()); + + Profile profile = new Profile(profileDto.getId(), profileName, profileDto.getDestinationAgencyIdentifier(), profileDto.getOriginatingAgencyIdentifier(), profileDto.getAttentionIndicator(), profileDto.getSourceAgency(), profileDto.getIsReportExclude(), profileDto.getFullName(), profileDto.getEmail(), profileDto.getPhoneNumber(), profileDto.getTitle(), profileDto.getDepartment(), + profileDto.getAddress(), userSetting); + + userObject.setProfile(profile); + + String encryptPassword = this.passwordEncoder.encode(userDto.getPassword()); + userObject.setPassword(encryptPassword); + + return userObject; + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/security/WebSecurityConfig.java b/web/src/main/java/org/mitre/iwp/web/security/WebSecurityConfig.java new file mode 100644 index 0000000..3186dca --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/security/WebSecurityConfig.java @@ -0,0 +1,101 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.security; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; +import org.springframework.web.filter.OncePerRequestFilter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Configuration +@ConditionalOnProperty(name = "iwp.security.secured", havingValue = "true") +public class WebSecurityConfig { + + private final IwpUserDetailsService userDetailsService; + + public WebSecurityConfig(@Lazy IwpUserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); + CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler(); + // set the name of the attribute the CsrfToken will be populated on + requestHandler.setCsrfRequestAttributeName("_csrf"); + + // Use desired login page + http.authorizeHttpRequests( + auth -> auth.requestMatchers("/css/*", "/fonts/*", "/login").permitAll() + .anyRequest().authenticated() + ) + .formLogin(formLogin -> formLogin + .defaultSuccessUrl("/", true) + .permitAll() + ) + .logout(logout -> logout.logoutSuccessUrl("/login").permitAll()).csrf(csrf -> csrf + .csrfTokenRepository(tokenRepository) + .csrfTokenRequestHandler(requestHandler) + ) + .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); + + // If you have issues with 403 errors on POST requests you may have CSRF issues. + // Be sure you are sending in the CSRF token header in with your request and with + // the correct header name (X-CSRF-TOKEN). You can use http.csrf(AbstractHttpConfigurer::disable) + // for troubleshooting but should not leave it disabled. + + return http.build(); + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); + } + +} + +final class CsrfCookieFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); + csrfToken.getToken(); + + filterChain.doFilter(request, response); + } + +} \ No newline at end of file diff --git a/web/src/main/java/org/mitre/iwp/web/service/AciiResponseRepository.java b/web/src/main/java/org/mitre/iwp/web/service/AciiResponseRepository.java new file mode 100644 index 0000000..39357e4 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/AciiResponseRepository.java @@ -0,0 +1,20 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.AciiResponse; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AciiResponseRepository extends JpaRepository { + AciiResponse findByImageId(Long imageId ); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/AnnotationRepository.java b/web/src/main/java/org/mitre/iwp/web/service/AnnotationRepository.java new file mode 100644 index 0000000..e578a23 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/AnnotationRepository.java @@ -0,0 +1,24 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.Annotation; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface AnnotationRepository extends JpaRepository { + + Optional findById(String id); + +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/AnnotationValueRepository.java b/web/src/main/java/org/mitre/iwp/web/service/AnnotationValueRepository.java new file mode 100644 index 0000000..a2291df --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/AnnotationValueRepository.java @@ -0,0 +1,22 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import java.util.Set; + +import org.mitre.iwp.web.model.AnnotationValue; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AnnotationValueRepository extends JpaRepository { + void deleteByAnnotation_IdIn(Set annotationIds); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/AppNotificationRepository.java b/web/src/main/java/org/mitre/iwp/web/service/AppNotificationRepository.java new file mode 100644 index 0000000..1e1a15a --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/AppNotificationRepository.java @@ -0,0 +1,23 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.AppNotification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.List; + +@Repository +public interface AppNotificationRepository extends JpaRepository { + List findByUserId(Long userId); + List findByUserIdAndIsNew(Long userId, boolean isNew); + long countByUserId(Long userId); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/BiqtResponseRepository.java b/web/src/main/java/org/mitre/iwp/web/service/BiqtResponseRepository.java new file mode 100644 index 0000000..e22db63 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/BiqtResponseRepository.java @@ -0,0 +1,20 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.BiqtResponse; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface BiqtResponseRepository extends JpaRepository { + BiqtResponse findByImageId(Long imageId ); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/ContactClassifierResponseRepository.java b/web/src/main/java/org/mitre/iwp/web/service/ContactClassifierResponseRepository.java new file mode 100644 index 0000000..5997727 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/ContactClassifierResponseRepository.java @@ -0,0 +1,20 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.ContactClassifierResponse; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ContactClassifierResponseRepository extends JpaRepository { + ContactClassifierResponse findByImageId(Long imageId ); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/CreateIrisSearchRequestRepository.java b/web/src/main/java/org/mitre/iwp/web/service/CreateIrisSearchRequestRepository.java new file mode 100644 index 0000000..20601f3 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/CreateIrisSearchRequestRepository.java @@ -0,0 +1,28 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.CreateIrisSearchRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface CreateIrisSearchRequestRepository extends JpaRepository { + List findByUser_Id(long id, Pageable sort); + + Optional findById(Long id); + + Long countAllByUser_Id(long id); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/DualPDMCompareResponseRepository.java b/web/src/main/java/org/mitre/iwp/web/service/DualPDMCompareResponseRepository.java new file mode 100644 index 0000000..430d08f --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/DualPDMCompareResponseRepository.java @@ -0,0 +1,18 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.mitre.iwp.web.model.DualPDMCompareResponse; + +public interface DualPDMCompareResponseRepository extends JpaRepository { + DualPDMCompareResponse findByProbeIdAndCandidateId(Long probeId, Long candidateId); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/DualPDMResponseRepository.java b/web/src/main/java/org/mitre/iwp/web/service/DualPDMResponseRepository.java new file mode 100644 index 0000000..ee0cfbb --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/DualPDMResponseRepository.java @@ -0,0 +1,20 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import org.mitre.iwp.web.model.DualPDMResponse; + +@Repository +public interface DualPDMResponseRepository extends JpaRepository { + DualPDMResponse findByImageId(Long imageId); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/EbtsImageRepository.java b/web/src/main/java/org/mitre/iwp/web/service/EbtsImageRepository.java new file mode 100644 index 0000000..e3862bb --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/EbtsImageRepository.java @@ -0,0 +1,20 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.data.EbtsImage; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface EbtsImageRepository extends JpaRepository { + EbtsImage findByImageHash(String imageHash); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/EbtsMessageRequestRepository.java b/web/src/main/java/org/mitre/iwp/web/service/EbtsMessageRequestRepository.java new file mode 100644 index 0000000..f97fca9 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/EbtsMessageRequestRepository.java @@ -0,0 +1,17 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.EbtsMessageRequest; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EbtsMessageRequestRepository extends JpaRepository { +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/ImageDataRepository.java b/web/src/main/java/org/mitre/iwp/web/service/ImageDataRepository.java new file mode 100644 index 0000000..fba6d51 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/ImageDataRepository.java @@ -0,0 +1,17 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.ImageData; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ImageDataRepository extends JpaRepository { +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/ImageFileRepository.java b/web/src/main/java/org/mitre/iwp/web/service/ImageFileRepository.java new file mode 100644 index 0000000..f821a80 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/ImageFileRepository.java @@ -0,0 +1,21 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.ImageFile; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ImageFileRepository extends JpaRepository { + ImageFile findByFileHash(String fileHash); + ImageFile findByImageHashContains(String imageHash); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/IrisAnnotationResponseRepository.java b/web/src/main/java/org/mitre/iwp/web/service/IrisAnnotationResponseRepository.java new file mode 100644 index 0000000..5bf5b32 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/IrisAnnotationResponseRepository.java @@ -0,0 +1,20 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.IrisAnnotationResponse; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface IrisAnnotationResponseRepository extends JpaRepository { + IrisAnnotationResponse findByImageId(Long imageId ); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/IrisMessageRepository.java b/web/src/main/java/org/mitre/iwp/web/service/IrisMessageRepository.java new file mode 100644 index 0000000..602a38f --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/IrisMessageRepository.java @@ -0,0 +1,24 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.data.IrisMessage; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface IrisMessageRepository extends JpaRepository { + IrisMessage findByType2UniversalControlNumber(String universalControlNumber); + List findByType1OriginatingAgencyIdentifier(String originatingAgencyIdentifier); + IrisMessage findByFileHash(String fileHash); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/IrisService.java b/web/src/main/java/org/mitre/iwp/web/service/IrisService.java new file mode 100644 index 0000000..8a1491f --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/IrisService.java @@ -0,0 +1,930 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.apache.commons.collections4.IterableUtils; +import org.apache.commons.lang3.StringUtils; +import org.jnbis.BitmapWithMetadata; +import org.jnbis.WSQDecoder; +import org.mitre.iwp.common.ImageUtils; +import org.mitre.iwp.mismatchrequest.EncounterType; +import org.mitre.iwp.mismatchrequest.IdentityType; +import org.mitre.iwp.mismatchrequest.ImageType; +import org.mitre.iwp.mismatchrequest.Request; +import org.mitre.iwp.support.EbtsTransactionCreator; +import org.mitre.iwp.web.IwpWebProperties; +import org.mitre.iwp.web.data.*; +import org.mitre.iwp.web.exception.InvalidMessageException; +import org.mitre.iwp.web.jms.ServiceMessageHandler; +import org.mitre.iwp.web.model.*; +import org.mitre.jet.ebts.Ebts; +import org.mitre.jet.ebts.EbtsBuilder; +import org.mitre.jet.ebts.EbtsParser; +import org.mitre.jet.ebts.Type7Handling; +import org.mitre.jet.ebts.records.GenericRecord; +import org.mitre.jet.ebts.records.LogicalRecord; +import org.mitre.jet.exceptions.EbtsBuildingException; +import org.mitre.jet.exceptions.EbtsParsingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; + +import javax.imageio.ImageIO; +import javax.xml.bind.DatatypeConverter; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import java.awt.image.BufferedImage; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.*; + +@Service +public class IrisService { + + private static final Logger log = LoggerFactory.getLogger(IrisService.class); + + private final IrisMessageRepository irisRepository; + + private final EbtsImageRepository imageRepository; + + private final ProfileRepository profileRepository; + + private final ImageFileRepository imageFileRepository; + + private final CreateIrisSearchRequestRepository createIrisSearchRequestRepository; + + private final ReportDataRepository reportDataRepository; + + private final ImageDataRepository imageDataRepository; + + private final UserAuthenticationRepository userAuthenticationRepository; + + private final ServiceMessageHandler serviceMessageHandler; + + private final IwpWebProperties iwpWebProperties; + + private final String imagePath; + + private final String uploadPath; + + private List skipCheckRecords; + + private static final String CREATE_DIR_ERR_MESSAGE = "Unable to create the directory {}"; + private static final String CREATE_FOLD_ERR_MESSAGE = "Unable to create folders for upload"; + private static final String CREATE_FILE_ERR_MESSAGE = "Unable to create file {}"; + private static final String DATA_NOT_FOUND_MESSAGE = "Ebts Image Data not found"; + + public IrisService(IrisMessageRepository irisRepository, EbtsImageRepository imageRepository, + ProfileRepository profileRepository, ImageFileRepository imageFileRepository, + CreateIrisSearchRequestRepository createIrisSearchRequestRepository, + ServiceMessageHandler serviceMessageHandler, + ReportDataRepository reportDataRepository, ImageDataRepository imageDataRepository, + UserAuthenticationRepository userAuthenticationRepository, + IwpWebProperties iwpWebProperties, @Value("${iwp.image.path}") String imagePath, + @Value("${iwp.upload.path}") String uploadPath) { + this.irisRepository = irisRepository; + this.imageRepository = imageRepository; + this.profileRepository = profileRepository; + this.imageFileRepository = imageFileRepository; + this.createIrisSearchRequestRepository = createIrisSearchRequestRepository; + this.reportDataRepository = reportDataRepository; + this.imageDataRepository = imageDataRepository; + this.userAuthenticationRepository = userAuthenticationRepository; + this.serviceMessageHandler = serviceMessageHandler; + this.iwpWebProperties = iwpWebProperties; + this.imagePath = imagePath; + this.uploadPath = uploadPath; + + this.skipCheckRecords = Arrays.asList(1, 2, 17); + } + + public Long createProfile(Profile profile) throws InvalidMessageException { + // Ensure name is unique + List currentList = listProfiles(); + + for (Profile prof : currentList) { + if (prof.getName().equals(profile.getName())) { + throw new InvalidMessageException( + String.format("Profile name '%s' already exists in system", profile.getName())); + } + } + + profileRepository.save(profile); + return profile.getId(); + } + + public List listProfiles() { + return IterableUtils.toList(profileRepository.findAll()); + } + + public Long updateProfile(Profile profile) throws InvalidMessageException { + // Ensure profile id exists in system + if (profile.getId() == null || profile.getId() == -1) { + throw new InvalidMessageException("Profile Id is empty"); + } + + Profile temp = profileRepository.findById(profile.getId()) + .orElseThrow(() -> new InvalidMessageException("Profile not found")); + if (null == temp) { + throw new InvalidMessageException(String.format("No profile found with id '%s'", profile.getId())); + } + + profileRepository.save(profile); + return profile.getId(); + } + + public void deleteProfile(long profileId) throws InvalidMessageException { + // Ensure profile id exists in system + if (profileId == -1) { + throw new InvalidMessageException("Profile Id is empty"); + } + + Optional profileOptional = profileRepository.findById(profileId); + if (profileOptional.isPresent()) { + userAuthenticationRepository.deleteByProfile(profileOptional.get()); + profileRepository.deleteById(profileId); + } else { + throw new InvalidMessageException(String.format("No profile found with id '%s'", profileId)); + } + } + + public CreateIrisSearchRequest saveSearchMessage(String contentType, byte[] imageData, Long userId) + throws NoSuchAlgorithmException, InvalidMessageException { + this.uploadImage(contentType, imageData, "", true); + + return this.submitCreateImage(contentType, imageData, userId); + } + + private String sanitizeHash(String hash) { + // Prevent propagation of malicious hashes + if (hash == null || !hash.matches("^[a-fA-F0-9]{64}$")) { + throw new IllegalArgumentException("Invalid hash"); + } + return hash; + } + + private String sanitizeContentType(String contentType) { + // Prevent propagation of malicious content types + List allowedTypes = Arrays.asList("tiff", "jpg", "jpeg", "png", "gif", "bmp", "unknown"); + String type = "unknown"; + if (contentType != null && contentType.contains("/")) { + type = contentType.split("/")[1].toLowerCase(); + } + if (!allowedTypes.contains(type)) { + throw new IllegalArgumentException("Invalid content type"); + } + return type; + } + + public String buildImagePath(String hash, String contentType) { + // Setup path for image to be saved + String sanitizedHash = sanitizeHash(hash); + String sanitizedContentType = sanitizeContentType(contentType); + + String directory = sanitizedHash.substring(0,2) + "/" + sanitizedHash.substring(2, 4); + String fileName = sanitizedHash + "." + sanitizedContentType; + + Path baseDir = Paths.get(imagePath).toAbsolutePath().normalize(); + Path filePath = baseDir.resolve(directory).resolve(fileName).normalize(); + + // Verify directory hierarchy + if (!filePath.startsWith(baseDir)) { + throw new SecurityException("Path traversal attempt detected"); + } + return filePath.toString(); + } + + public void saveImageFile(String path, byte[] imageData) throws IOException, InvalidMessageException{ + // Ensure directory exists and try to save image to file + Path filePath = Paths.get(path).toAbsolutePath().normalize(); + Path baseDir = Paths.get(imagePath).toAbsolutePath().normalize(); + + // Verify hierarchy + if (!filePath.startsWith(baseDir)) { + throw new SecurityException("Path traversal attempt detected"); + } + File dir = filePath.getParent().toFile(); + if (!dir.mkdirs() && !dir.exists()) { + log.error(CREATE_DIR_ERR_MESSAGE, dir.getPath()); + throw new InvalidMessageException(CREATE_FOLD_ERR_MESSAGE); + } + + try (FileOutputStream fos = new FileOutputStream(filePath.toFile())) { + log.info("Creating image file {}", filePath); + fos.write(imageData); + fos.flush(); + } + } + + public EbtsImage createAndSaveImage(String contentType, String hash, String path) { + // Create EbtsImage instance for the image and save to DB + EbtsImage image = new EbtsImage(); + image.setOriginalImageType(contentType); + image.setImageHash(hash); + image.setPath(path); + + return imageRepository.save(image); + } + + private void updateImageFile(String srbHash, String hash) { + // Update previously made file with EbtsImage + ImageFile sFile = this.imageFileRepository.findByFileHash(srbHash); + if (sFile != null) { + if (!sFile.getImageHash().contains(hash)) { + sFile.getImageHash().add(hash); + this.imageFileRepository.save(sFile); + } + } else { + sFile = new ImageFile(); + sFile.setFileHash(srbHash); + sFile.getImageHash().add(hash); + this.imageFileRepository.save(sFile); + } + } + + public Long uploadImage(String contentType, byte[] imageData, String srbHash, boolean isType17) + throws NoSuchAlgorithmException, InvalidMessageException { + log.info("uploadImage content-type:{}, bytes:{}", contentType, imageData.length); + if (contentType == null || contentType.isEmpty()) { + log.info("Setting content type to unknown"); + contentType = "image/unknown"; + } + + // hash the image bytes + String hash = this.getHash(imageData); + + EbtsImage image = imageRepository.findByImageHash(hash); + + // If the image doesn't not exist, create it + if (image == null) { + String path = buildImagePath(hash, contentType); + log.info("Image NOT found in db, has HASH: {}", hash); + try { + saveImageFile(path, imageData); + } catch (IOException e){ + throw new InvalidMessageException(e.getMessage()); + } + + try { + image = createAndSaveImage(contentType, hash, path); + } catch (DataIntegrityViolationException e) { + image = imageRepository.findByImageHash(hash); + } + + // Process all services upon initial image upload + if (isType17 && this.checkSize(imageData)) { + this.serviceMessageHandler.processImageDefault(image); + } + + if (StringUtils.isBlank(srbHash)) + srbHash = hash; + + updateImageFile(srbHash, hash); + } + + return image.getId(); + } + + private boolean checkSize(byte[] imageData) { + // Images should be <= 640 width and 480 height + try { + BufferedImage bi = ImageIO.read(new ByteArrayInputStream(imageData)); + log.info("Width: {} x Height: {}", bi.getWidth(), bi.getHeight()); + if (bi.getWidth() <= this.iwpWebProperties.getMaxWidthImage() && + bi.getHeight() <= this.iwpWebProperties.getMaxHeightImage()) { + log.info("Size is valid"); + return true; + } + log.info("Size is Horrific"); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + + return false; + } + + public String getImageData(CreateIrisSearchRequest searchRequest) throws InvalidMessageException, IOException { + EbtsImage ebtsImage = this.imageRepository + .findById(searchRequest.getMessage().getImageData().getEbtsImageId()) + .orElseThrow(() -> new InvalidMessageException(DATA_NOT_FOUND_MESSAGE)); + byte[] imageData = EbtsImage.getImageBytesPng(ebtsImage); + return new String(Base64.getEncoder().encode(imageData)); + } + + public boolean submitSearchRequest(long searchId) + throws IOException, InvalidMessageException, EbtsBuildingException { + log.info("submitSearchRequest for id: {}", searchId); + // get the message from mysql + Optional searchRequestOptional = createIrisSearchRequestRepository.findById(searchId); + if (!searchRequestOptional.isPresent()) { + log.error("Invalid id {}", searchId); + return false; + } + CreateIrisSearchRequest searchRequest = searchRequestOptional.get(); + + EbtsMessageRequest tempMessage = searchRequest.getMessage(); + tempMessage.setDateOfSubmission(new Date().toString()); + searchRequest.setMessage(tempMessage); + + byte[] ebtsFileBytes = this.buildEbtsMessage(searchRequest.getMessage()); + + if (ebtsFileBytes.length != 0) { + String filename = new SimpleDateFormat("'generatedEbts_'yyyyMMddHHmm'.eft'").format(new Date()); + new ByteArrayResource(ebtsFileBytes) { + @Override + public String getFilename() { + return filename; + } + }; + + return true; + } else { + return false; + } + } + + public long uploadImage(String contentType, String imageData) + throws NoSuchAlgorithmException, InvalidMessageException { + byte[] imageBytes = Base64.getDecoder().decode(imageData); + return uploadImage(contentType, imageBytes, "", true); + } + + public byte[] getImage(long imageId) throws InvalidMessageException { + EbtsImage image = imageRepository.findById(imageId) + .orElseThrow(() -> new InvalidMessageException(DATA_NOT_FOUND_MESSAGE)); + + if (image == null) { + return new byte[0]; + } + + try { + log.info("Getting bytes for image at {}", image.getPath()); + byte[] buffer = EbtsImage.getImageBytes(image); + log.info("Image Size: {}", buffer.length); + return this.convertImage(image.getOriginalImageType(), buffer); + } catch (IOException e) { + log.error("Unable to get image {}", e.getMessage()); + return new byte[0]; + } + + } + + public EbtsImage getEbtsImage(long imageId) throws InvalidMessageException { + return imageRepository.findById(imageId).orElseThrow(() -> new InvalidMessageException(DATA_NOT_FOUND_MESSAGE)); + } + + public byte[] getImageThumbnail(long imageId) throws InvalidMessageException, IOException { + EbtsImage image = imageRepository.findById(imageId) + .orElseThrow(() -> new InvalidMessageException(DATA_NOT_FOUND_MESSAGE)); + if (image == null) { + return new byte[0]; + } + + return EbtsImage.buildThumbnail(image); + } + + public ReportData submitIrisXmlRequest(byte[] irisFile, Long userId, String fileName) + throws NoSuchAlgorithmException, InvalidMessageException { + String xmlHash = this.getHash(irisFile); + ReportData response = new ReportData(); + if (irisFile != null && irisFile.length > 0) { + Request req = parseXmlRequest(irisFile); + response.setResponseType(ResponseType.XML); + response.getProbe().setSubjectIdentifier(req.getProbe().getUcn()); + + log.info("Not null or empty! Printing the xml Hash: {}\n", xmlHash); + for (ImageType image : req.getProbe().getEncounter().getImages()) { + response.getProbe().getImageList().add(this.createImageData(image, xmlHash)); + } + + for (IdentityType ident : req.getTargets().getTarget()) { + Candidate candidate = new Candidate(); + candidate.setSubjectIdentifier(ident.getUcn()); + response.getCandidates().add(candidate); + for (EncounterType encounter : ident.getEncounters().getEncounter()) { + for (ImageType image : encounter.getImages()) { + candidate.getImageList().add(this.createImageData(image, xmlHash)); + } + } + } + } else { + // If file is null or empty, nothing to do + response.getErrorMessages().add("Input XML file is empty"); + + } + + UserAuthentication ua = userAuthenticationRepository.findById(userId).orElse(null); + if (response.getCreatedBy() == null || response.getCreatedBy().getId() == null) + response.setCreatedBy(ua); + response.setWorkedBy(ua); + response.getCaseInformation().setReceivedDate(Instant.now().toEpochMilli()); + response.getCaseInformation().setComparisonDate(Instant.now().toEpochMilli()); + response.setFileName(fileName); + + this.reportDataRepository.save(response); + return response; + } + + public ImageData createImageData(String contentType, byte[] imageData) + throws NoSuchAlgorithmException, InvalidMessageException { + String hash = this.getHash(imageData); + log.info("Image Hash: {}", hash); + ImageData iData = new ImageData(); + iData.setEbtsImageId(this.uploadImage(contentType, imageData, hash, true)); + return this.imageDataRepository.save(iData); + } + + public ImageData createImageData(ImageType image, String xmlHash) + throws NoSuchAlgorithmException, InvalidMessageException { + ImageData imageData = new ImageData(this.uploadImage("image/png", image.getValue(), xmlHash, true)); + if (image.getDme() != null) { + imageData.setDme(image.getDme().toString()); + } + imageData.setEyeLabel(EyeLabel.valueOf(image.getElr().intValue())); + + imageData = this.imageDataRepository.save(imageData); + this.imageDataRepository.flush(); + return imageData; + } + + public void saveIrisMessage(IrisMessage irisMessage, String srbHash) throws NoSuchAlgorithmException { + // go through the images and save them in the ebts images collection + irisRepository.findByFileHash(srbHash); + List t17List = irisMessage.getType17(); + for (Type17 type17 : t17List) { + try { + type17.setEbtsImageId(this.uploadImage(Type17.getContentType(type17.getCompressionAlgorithm()), + type17.getData(), srbHash, true)); + } catch (InvalidMessageException e) { + log.error("Error saving iris message image: {}", type17.getCompressionAlgorithm()); + } + } + + irisRepository.save(irisMessage); + } + + public ReportData saveReportData(byte[] fileInfo, Long userId, boolean isSrb) + throws InvalidMessageException, NoSuchAlgorithmException { + String fileName = UUID.randomUUID().toString(); + String directory = uploadPath; + String path = directory + File.separator + fileName; + + File dir = new File(directory); + if (!dir.mkdirs() && !dir.exists()) { + log.error(CREATE_DIR_ERR_MESSAGE, directory); + throw new InvalidMessageException(CREATE_FOLD_ERR_MESSAGE); + } + + try (FileOutputStream fos = new FileOutputStream(path)) { + log.info("Creating file {}", path); + fos.write(fileInfo); + fos.flush(); + } catch (IOException e) { + log.error(CREATE_FILE_ERR_MESSAGE, fileName); + throw new InvalidMessageException(e.getMessage()); + } + + if (isSrb) { + return this.submitSrb(fileInfo, userId, fileName); + } else { + return this.submitIrisXmlRequest(fileInfo, userId, fileName); + } + } + + public byte[] getSrbData(String fileName) throws InvalidMessageException, IOException { + String path = uploadPath + File.separator + fileName; + File f = new File(path); + if (!f.exists()) { + log.error("Unable to find srb file {}", path); + throw new InvalidMessageException("Unable to find srb file : " + path); + } + return Files.readAllBytes(f.toPath()); + } + + public ReportData submitSrb(byte[] srbInfo, Long userId, String fileName) throws NoSuchAlgorithmException { + String srbHash = this.getHash(srbInfo); + ReportData response = new ReportData(); + + try { + Ebts ebts = EbtsParser.parse(srbInfo, Type7Handling.FLEX); + IrisMessage irisMessage = this.irisRepository.findByFileHash(srbHash); + if (irisMessage == null) { + irisMessage = IrisMessage.buildFromEbts(ebts); + this.saveIrisMessage(irisMessage, srbHash); + } + response = getReportDataResponse(irisMessage); + } catch (EbtsParsingException exc) { + log.warn("Failed to parse EBTS File", exc); + response.addErrorMessages(exc.getMessage()); + } + + UserAuthentication ua = userAuthenticationRepository.findById(userId).orElse(null); + if (response.getCreatedBy() == null || response.getCreatedBy().getId() == null) + response.setCreatedBy(ua); + response.setWorkedBy(ua); + + response.getCaseInformation().setReceivedDate(Instant.now().toEpochMilli()); + response.getCaseInformation().setComparisonDate(Instant.now().toEpochMilli()); + response.setFileName(fileName); + response = this.reportDataRepository.save(response); + + return response; + } + + public ReportData getReportDataResponse(IrisMessage irisMessage) { + ReportData response = new ReportData(); + + if (irisMessage.getType1() == null) { + String errorMessage = "Missing Type 1 Record"; + response.getErrorMessages().add(errorMessage); + log.debug(errorMessage); + } else { + Type1 t1 = irisMessage.getType1(); + response.getProbe().setTransactionControlNumber(t1.getTransactionControlNumber()); + response.getProbe().setTransactionControlReference(t1.getTransactionControlReference()); + } + + List cilList = new ArrayList<>(); + if (irisMessage.getType2() == null) { + String errorMessage = "Missing Type 2 Record"; + response.getErrorMessages().add(errorMessage); + log.debug(errorMessage); + } else { + Type2 t2 = irisMessage.getType2(); + + if (!t2.getStatusErrorMessage().isEmpty()) { + response.setResponseType(ResponseType.ERRB); + for (String errorMessage : t2.getStatusErrorMessage()) { + response.getErrorMessages().add(errorMessage); + log.debug(errorMessage); + } + + } else { + response.setResponseType(ResponseType.SRB); + if (t2.getCandidateInvesitgativeListField() == null + || t2.getCandidateInvesitgativeListField().isEmpty()) { + String errorMessage = "Missing Typ2 CNL Field"; + response.getErrorMessages().add(errorMessage); + log.debug(errorMessage); + } else { + cilList = t2.getCandidateInvesitgativeListField(); + } + + if (irisMessage.getType17() == null) { + String errorMessage = "Missing Type 17 Record"; + response.getErrorMessages().add(errorMessage); + log.debug(errorMessage); + } else { + List t17List = irisMessage.getType17(); + + if (t17List.isEmpty()) { + String errorMessage = "Type 17 Record not contain image data"; + response.getErrorMessages().add(errorMessage); + log.debug(errorMessage); + } else if (t17List.size() == 1) { + String errorMessage = "Type 17 Record missing probe image data"; + response.getErrorMessages().add(errorMessage); + log.debug(errorMessage); + } + + if (t17List.size() != cilList.size() + 1) { + String errorMessage = String.format( + "Type 17 record size %s does not correspond to Type 2 Candidate List Size %s", + t17List.size(), cilList.size()); + response.getErrorMessages().add(errorMessage); + log.debug(errorMessage); + } else { + + for (int i = 0; i < t17List.size(); i++) { + Type17 t17 = t17List.get(i); // Probe Image + if (t17.getData() == null) { + String errorMessage = String.format("Image data missing from Type17 Field %s", i); + response.getErrorMessages().add(errorMessage); + log.debug(errorMessage); + } else { + if (i == 0) { + ImageData imageData = new ImageData(t17.getEbtsImageId()); + imageData.setRecordType(17); + imageData = this.imageDataRepository.save(imageData); + response.getProbe().getImageList().add(imageData); + } else { + CandidateInvesitgativeListField cil = cilList.get(i - 1); + Candidate cand = new Candidate(); + ImageData imageData = new ImageData(t17.getEbtsImageId()); + imageData.setRecordType(17); + imageData = this.imageDataRepository.save(imageData); + cand.getImageList().add(imageData); + cand.setScore(Integer.toString(cil.getMatchScore())); + cand.setSubjectIdentifier(cil.getSubjectIdentifier()); + response.getCandidates().add(cand); + } + } + } + } + } + } + } + + return response; + } + + public byte[] buildEbtsMessage(EbtsMessageRequest iMess) + throws IOException, InvalidMessageException, EbtsBuildingException { + Ebts ebts = new Ebts(); + if (iMess == null) { + EbtsTransactionCreator creator = new EbtsTransactionCreator(); + ebts = creator.createDefault(); + } else if (iMess.isValid()) { + LogicalRecord t1Record = new GenericRecord(1); + LogicalRecord t2Record = new GenericRecord(2); + LogicalRecord t17Record = new GenericRecord(17); + + // Handle Type 1 Record Mandatory Fields + t1Record.setField(4, EbtsConversionHelper.getFieldFromString("EBTS")); + t1Record.setField(5, EbtsConversionHelper.getFieldFromString(iMess.getDateOfSubmission())); + t1Record.setField(7, EbtsConversionHelper.getFieldFromString(iMess.getDestinationAgencyIdentifier())); + t1Record.setField(8, EbtsConversionHelper.getFieldFromString(iMess.getOriginatingAgencyIdentifier())); + t1Record.setField(9, EbtsConversionHelper.getFieldFromString(iMess.getTransactionControlNumber())); + + // Handle Type 2 Record Mandatory Fields + t2Record.setField(6, EbtsConversionHelper.getFieldFromString(iMess.getAttentionIndicator())); + t2Record.setField(10, + EbtsConversionHelper.getFieldFromString(iMess.getCinPrefix(), iMess.getCinIdentifier())); + t2Record.setField(11, EbtsConversionHelper.getFieldFromString(Integer.toString(iMess.getCaseExtension()))); + t2Record.setField(79, + EbtsConversionHelper.getFieldFromString(Integer.toString(iMess.getNumberOfCandidates()))); + + // Handle Type 17 Record Mandatory Fields + t17Record.setField(3, EbtsConversionHelper + .getFieldFromString(Integer.toString(iMess.getImageData().getEyeLabel().getValue()))); + t17Record.setField(4, EbtsConversionHelper.getFieldFromString(iMess.getSourceAgency())); + t17Record.setField(5, EbtsConversionHelper.getFieldFromString(iMess.getIrisCaptureDate())); + + if (iMess.getRotationOfEye() >= 0) { + t17Record.setField(14, + EbtsConversionHelper.getFieldFromString(Integer.toString(iMess.getRotationOfEye()))); + t17Record.setField(15, + EbtsConversionHelper.getFieldFromString(Integer.toString(iMess.getRotationUncertainty()))); + } + + if (iMess.getImageData() != null) { + + byte[] imageData = Base64.getDecoder().decode(iMess.getImageData().getImageData()); + InputStream in = new ByteArrayInputStream(imageData); + BufferedImage image = ImageIO.read(in); + + t17Record.setImageData(imageData); // Shouldn't need to convert bytes??? + t17Record.setField(6, EbtsConversionHelper.getFieldFromInt(image.getWidth())); + t17Record.setField(7, EbtsConversionHelper.getFieldFromInt(image.getHeight())); + t17Record.setField(9, EbtsConversionHelper.getFieldFromInt(image.getWidth())); + t17Record.setField(10, EbtsConversionHelper.getFieldFromInt(image.getHeight())); + + // Check ANN conditions + if (iMess.getImageData().getId() == -1) { + EbtsImage eImage = imageRepository.findById(iMess.getImageData().getEbtsImageId()) + .orElseThrow(() -> new InvalidMessageException(DATA_NOT_FOUND_MESSAGE)); + String imageType = eImage.getOriginalImageType(); + if (this.needs902(imageType)) { + // Get Current Date/Time in GMT - 2013-08-01T02:23:44Z + Date currDate = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); + sdf.setTimeZone(TimeZone.getTimeZone("GMT")); + + String nav = "APN-IWP"; + String own = "MITRE"; + String pro = String.format("MITRE developed algorithm to convert '%s' to 'image/png'", + imageType); + + t17Record.setField(902, + EbtsConversionHelper.getFieldFromString(sdf.format(currDate), nav, own, pro)); + } + } + } + + ebts.addRecord(t1Record); + ebts.addRecord(t2Record); + ebts.addRecord(t17Record); + } else { + log.debug(iMess.getErrors()); + } + + EbtsBuilder eBuilder = new EbtsBuilder(); + return eBuilder.build(ebts); + } + + public byte[] storeEbtsFile(CreateIrisSearchRequest createIrisSearchRequest) + throws InvalidMessageException, NoSuchAlgorithmException, IOException, EbtsBuildingException { + byte[] ebtsFile = this.buildEbtsMessage(createIrisSearchRequest.getMessage()); + // Saving PDF to upload path, if the PDF was created successfully + if (ebtsFile != null) { + String hash = this.getHash(ebtsFile); + String directory = uploadPath + "/" + hash.substring(0, 2) + "/" + hash.substring(2, 4); + String path = String.format("%s/%s.ebts", directory, hash); + + File f = new File(path); + log.info("File exists:{} path:{}", f.exists(), path); + + File dir = new File(directory); + if (!dir.mkdirs() && !dir.exists()) { + log.error(CREATE_DIR_ERR_MESSAGE, directory); + throw new InvalidMessageException(CREATE_FOLD_ERR_MESSAGE); + } + + createIrisSearchRequest.setFilePath(path); + + this.createIrisSearchRequestRepository.save(createIrisSearchRequest); + try (FileOutputStream fos = new FileOutputStream(path)) { + log.info("Creating pdf file {}", path); + fos.write(ebtsFile); + fos.flush(); + } catch (IOException e) { + log.error(CREATE_FILE_ERR_MESSAGE, createIrisSearchRequest.getFilePath()); + throw new InvalidMessageException(e.getMessage()); + } + } + + return ebtsFile; + } + + public boolean needs902(String imageType) { + String temp = imageType.toLowerCase(); + return temp.endsWith("jpeg") || temp.endsWith("jpg"); + } + + public Request parseXmlRequest(byte[] irisFile) { + try { + + JAXBContext jaxbContext = JAXBContext.newInstance(Request.class); + + XMLInputFactory xif = XMLInputFactory.newFactory(); + xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); + xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); + XMLStreamReader xsr = xif.createXMLStreamReader(new ByteArrayInputStream(irisFile)); + + Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller(); + return (Request) jaxbUnmarshaller.unmarshal(xsr); + } catch (JAXBException | XMLStreamException exception) { + log.debug(exception.getMessage()); + return null; + } + } + + private byte[] convertImage(String contentType, byte[] imageData) throws IOException { + if (contentType.equals("image/png")) { + return imageData; + } + + contentType = contentType.replace("image/", "").toUpperCase(); + BufferedImage img; + + if (contentType.equalsIgnoreCase("wsq20")) { + BitmapWithMetadata bitmapWithMetadata = WSQDecoder.decode(new ByteArrayInputStream(imageData)); + imageData = bitmapWithMetadata.getPixels(); + contentType = "NONE"; + img = ImageUtils.read(contentType, bitmapWithMetadata.getWidth(), bitmapWithMetadata.getHeight(), + 8, imageData); + } else { + img = ImageUtils.read(contentType, 16, 16, 8, imageData); + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ImageIO.write(img, "png", outputStream); + + return outputStream.toByteArray(); + } + + public String getHash(byte[] byteArray) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(byteArray); + byte[] digest = md.digest(); + return DatatypeConverter.printHexBinary(digest).toUpperCase(); + } + + public CreateIrisSearchRequest submitCreateImage(String mimeType, byte[] bytes, Long userId) { + CreateIrisSearchRequest createIrisSearchRequest = new CreateIrisSearchRequest(); + EbtsMessageRequest request = new EbtsMessageRequest(); + try { + request.setImageData(this.createImageData(mimeType, bytes)); + + Date irisCaptureDate = new Date(); + SimpleDateFormat dateToString = new SimpleDateFormat("yyyyMMdd"); + String irisCaptureDateString = dateToString.format(irisCaptureDate); + request.setIrisCaptureDate(irisCaptureDateString); + } catch (Exception e) { + request.addErrorMessages(e.getMessage()); + } + + createIrisSearchRequest.setMessage(request); + createIrisSearchRequest.setUser(userAuthenticationRepository.findById(userId).orElse(null)); + + createIrisSearchRequest.setCreationDateEpoch(Instant.now().toEpochMilli()); + return this.createIrisSearchRequestRepository.save(createIrisSearchRequest); + } + + public void createTshepiiCompareRequest(Long probeId, Long candidateId) { + this.serviceMessageHandler.requestTshepiiComparison(probeId, candidateId); + } + + public boolean isMultiModal(byte[] srbFile) throws EbtsParsingException { + // Assume if the file contains image data thats NOT in type 17 records, it's a + // multimodal request + Ebts ebts = EbtsParser.parse(srbFile, Type7Handling.FLEX); + + Map recordListing = ebts.getLogicalRecordCounts(); + + for (Map.Entry entry : recordListing.entrySet()) { + Integer recordNumber = entry.getKey(); + + if (this.skipCheckRecords.contains(recordNumber)) { + // skip + continue; + } + + if (recordListing.get(recordNumber) > 0) { + List recordList = ebts.getRecordsByType(recordNumber); + + for (LogicalRecord logicalRecord : recordList) { + if (logicalRecord.getImageData().length > 0) { + return true; + } + } + } + } + + return false; + } + + public List createImageDataFromEbts(byte[] srbFile) throws EbtsParsingException { + List imageDataList = new ArrayList<>(); + + Ebts ebts = EbtsParser.parse(srbFile, Type7Handling.FLEX); + Map occurrenceCount = new HashMap<>(); + + for (LogicalRecord logicalRecord : ebts.getAllRecords()) { + if (logicalRecord.getRecordType() == 1 || + logicalRecord.getRecordType() == 2) { + // skip + continue; + } + + if (logicalRecord.getImageData() == null || logicalRecord.getImageData().length == 0) { + // nothing to do + continue; + } + + occurrenceCount.merge(logicalRecord.getRecordType(), 1, Integer::sum); + + byte[] imageData = logicalRecord.getImageData(); + String contentType = "image/none"; + if (logicalRecord.getRecordType() < 10) { + if (logicalRecord.hasField(8)) { + contentType = Type17.getBinaryContentType(logicalRecord.getField(8).toString()); + } + } else { + if (logicalRecord.hasField(11)) { + contentType = Type17.getContentType(logicalRecord.getField(11).toString()); + } + } + try { + String hash = this.getHash(imageData); + log.info("Image Hash: {}", hash); + log.info("Content Type {}", contentType); + ImageData iData = new ImageData(); + iData.setImageNotes(String.valueOf(logicalRecord.getRecordType())); + iData.setEbtsImageId( + this.uploadImage(contentType, imageData, hash, logicalRecord.getRecordType() == 17)); + iData.setEyeLabel(EyeLabel.UNDEFINED); + imageDataList.add(this.imageDataRepository.save(iData)); + iData.setRecordType(logicalRecord.getRecordType()); + iData.setRecordIndex(occurrenceCount.get(logicalRecord.getRecordType())); + + } catch (Exception e) { + log.info(e.getMessage()); + } + } + + return imageDataList; + } + +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/NotificationService.java b/web/src/main/java/org/mitre/iwp/web/service/NotificationService.java new file mode 100644 index 0000000..53305b3 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/NotificationService.java @@ -0,0 +1,160 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.apache.commons.collections4.IterableUtils; +import org.mitre.iwp.web.exception.InvalidMessageException; +import org.mitre.iwp.web.model.AppNotification; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.List; +import java.util.Optional; + +@Service +public class NotificationService { + + private static final Logger logger = LoggerFactory.getLogger(NotificationService.class); + + private final AppNotificationRepository repository; + + private static final String idEmptyMessage = "UserNotification Id is empty"; + private static final String notificationNotFoundMessage = "No notification found with id '%s'"; + + public NotificationService(AppNotificationRepository repository) { + this.repository = repository; + } + + public List getNotificationsForUser(long userId) { + return repository.findByUserId(userId); + } + + public Long getNotificationCountForUser(long userId) { + return repository.countByUserId(userId); + } + + /*** + * + * @param id + * @return individual notification + */ + public AppNotification getNotification(long id) { + Optional userOption = repository.findById(id); + if(userOption.isPresent()) { + return userOption.get(); + } + + return null; + } + + /*** + * Adds a notification to the database + * + * @param userId + * @param message + * @return + * @throws InvalidMessageException + */ + public Long createUserNotification(long userId, String message) throws InvalidMessageException { + AppNotification appNotification = new AppNotification(); + appNotification.setUserId(userId); + appNotification.setMessage(message); + appNotification.setIsNew(true); + appNotification.setDateCreated(Instant.now().toString()); + appNotification.setIcon("notifications"); + return this.createUserNotification(appNotification); + } + + /*** + * Adds a notification to the database + * + * @param notification + * @return + * @throws InvalidMessageException + */ + public Long createUserNotification(AppNotification notification) throws InvalidMessageException { + if (notification.getUserId() == null) { + throw new InvalidMessageException(String.format("UserNotification %s missing user ", notification.getId())); + } + + repository.save(notification); + return notification.getId(); + } + + /*** + * Gets a list of all the notifications in the system + * + * @return + */ + public List listUserNotifications() { + return IterableUtils.toList(repository.findAll()); + } + + /*** + * Updates the notification + * + * @param notification + * @return + * @throws InvalidMessageException + */ + public void updateUserNotification(AppNotification notification) throws InvalidMessageException { + // Ensure notification id exists in system + if (notification.getId() == -1) { + throw new InvalidMessageException(idEmptyMessage); + } + + repository.save(notification); + } + + /*** + * + * @param notificationId + * @param status NotificationStatus + * @return + * @throws InvalidMessageException + */ + public void setStatusUserNotification(long notificationId, String status) throws InvalidMessageException { + // Ensure notification id exists in system + if (notificationId == -1) { + throw new InvalidMessageException(idEmptyMessage); + } + AppNotification not = repository.findById(notificationId) + .orElseThrow(() -> new InvalidMessageException(String.format(notificationNotFoundMessage, notificationId))); + + not.setIsNew(false); + repository.save(not); + } + + /*** + * Deletes a notification + * + * @param notificationId + * @return + * @throws InvalidMessageException + */ + public boolean deleteUserNotification(long notificationId) throws InvalidMessageException { + logger.debug("Delete UserNotification id:{} ", notificationId); + // Ensure notification id exists in system + if (notificationId == -1) { + throw new InvalidMessageException(idEmptyMessage); + } + + if (repository.existsById(notificationId)) { + repository.deleteById(notificationId); + return true; + } + + throw new InvalidMessageException(String.format(notificationNotFoundMessage, notificationId)); + } + +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/PdmDeformedImageResponseRepository.java b/web/src/main/java/org/mitre/iwp/web/service/PdmDeformedImageResponseRepository.java new file mode 100644 index 0000000..9a9143a --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/PdmDeformedImageResponseRepository.java @@ -0,0 +1,20 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.PdmDeformedImageResponse; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PdmDeformedImageResponseRepository extends JpaRepository { + PdmDeformedImageResponse findByDeformedId(String deformedId); +} \ No newline at end of file diff --git a/web/src/main/java/org/mitre/iwp/web/service/PdmResponseRepository.java b/web/src/main/java/org/mitre/iwp/web/service/PdmResponseRepository.java new file mode 100644 index 0000000..ed4607b --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/PdmResponseRepository.java @@ -0,0 +1,20 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.PdmResponse; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PdmResponseRepository extends JpaRepository { + PdmResponse findByImageId(Long imageId); +} \ No newline at end of file diff --git a/web/src/main/java/org/mitre/iwp/web/service/PersonRepository.java b/web/src/main/java/org/mitre/iwp/web/service/PersonRepository.java new file mode 100644 index 0000000..5c78fcf --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/PersonRepository.java @@ -0,0 +1,20 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.Person; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PersonRepository extends JpaRepository { + +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/PrivilegeRepository.java b/web/src/main/java/org/mitre/iwp/web/service/PrivilegeRepository.java new file mode 100644 index 0000000..346675c --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/PrivilegeRepository.java @@ -0,0 +1,20 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.Privilege; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PrivilegeRepository extends JpaRepository { + Privilege findByName(String name); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/ProfileRepository.java b/web/src/main/java/org/mitre/iwp/web/service/ProfileRepository.java new file mode 100644 index 0000000..b21923d --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/ProfileRepository.java @@ -0,0 +1,19 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.Profile; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProfileRepository extends JpaRepository { +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/ReportDataRepository.java b/web/src/main/java/org/mitre/iwp/web/service/ReportDataRepository.java new file mode 100644 index 0000000..ea1e1d1 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/ReportDataRepository.java @@ -0,0 +1,38 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.ReportData; +import org.mitre.iwp.web.model.UserAuthentication; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface ReportDataRepository extends JpaRepository { + List findByCreatedBy_Id(long id, Pageable sort); + List findByWorkedBy_Id(long id, Pageable sort); + List findByCreatedBy(UserAuthentication ua, Pageable sort); + List findByWorkedBy(UserAuthentication ua, Pageable sort); + List findByCreatedByOrWorkedBy(UserAuthentication cua, UserAuthentication wua, Pageable sort); + + Long countAllByCreatedBy_Id(long id); + Long countAllByWorkedBy_Id(long id); + Long countAllByCreatedBy_IdOrWorkedBy_Id(long cid, long wid); + Long countAllByCreatedBy(UserAuthentication ua); + Long countAllByWorkedBy(UserAuthentication ua); + Long countAllByCreatedByOrWorkedBy(UserAuthentication cua, UserAuthentication wua); + + List findByFileName(String fileName); + +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/ReportDataService.java b/web/src/main/java/org/mitre/iwp/web/service/ReportDataService.java new file mode 100644 index 0000000..f7c1d16 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/ReportDataService.java @@ -0,0 +1,161 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.springframework.stereotype.Service; +import org.mitre.iwp.web.model.*; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashSet; +import java.util.Set; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Service +public class ReportDataService { + + private static final Logger log = LoggerFactory.getLogger(ReportDataService.class); + + @PersistenceContext + private EntityManager em; + + private AnnotationValueRepository avRepository; + + public ReportDataService(AnnotationValueRepository annotationValueRepository) { + this.avRepository = annotationValueRepository; + } + + @Transactional(rollbackFor = Exception.class) + public ReportData saveReportData(ReportData reportData) { + ReportData oldReportData = reportData.getId() != null ? em.find(ReportData.class, reportData.getId()) : null; + + // If there's no old report data, we can just save the item + if (oldReportData == null) { + log.info("No old report data is found -- save report"); + return mergeReportData(reportData); + } + + // In the case of an existing reportData, we're going to have to do some + // removal on our own because of the current way we're saving entities... + + // Because of the weird situation we have; if an annotation is being deleted + // We need to handle that first + // So find what annotations used to exist. + log.info("Finding associated annotations of old report"); + Set oldAnnotations = new HashSet<>(); + if (oldReportData != null) { + log.info("Going through candidates first..."); + for (Candidate c : oldReportData.getCandidates()) { + for (ImageData img : c.getImageList()) { + for (Annotation annotation : img.getAnnotations()) { + collectAnnotationIds(oldAnnotations, annotation); + } + } + } + + log.info("Going through probe..."); + for (ImageData img : oldReportData.getProbe().getImageList()) { + for (Annotation annotation : img.getAnnotations()) { + collectAnnotationIds(oldAnnotations, annotation); + + } + } + } + + // Compare this to the new annotations we're trying to write + Set newAnnotations = new HashSet<>(); + + log.info("Finding associated annotations of new report"); + log.info("Going through candidates first..."); + for (Candidate c : reportData.getCandidates()) { + for (ImageData img : c.getImageList()) { + for (Annotation annotation : img.getAnnotations()) { + collectAnnotationIds(newAnnotations, annotation); + } + } + } + + log.info("Going through probe..."); + for (ImageData img : reportData.getProbe().getImageList()) { + for (Annotation annotation : img.getAnnotations()) { + collectAnnotationIds(newAnnotations, annotation); + } + } + + // Do the delete + cleanUpAnnotations(oldAnnotations, newAnnotations); + + // Now try to do the merge + return mergeReportData(reportData); + } + + /** + * Save reportData into the database. + * + * @param reportData + * @return Saved report data (and resulting ids) + */ + @Transactional(rollbackFor = Exception.class) + public ReportData mergeReportData(ReportData reportData) { + log.info("Attempting to save report..."); + return em.merge(reportData); + } + + /** + * Remove associated annotation values of deleted annotations. + * + * @param oldAnno Set of ids of old annotations + * @param newAnno Set of ids for new annotations + */ + @Transactional(rollbackFor = Exception.class) + public void cleanUpAnnotations(Set oldAnno, Set newAnno) { + // Take the Set difference between old annotations and new annotations + log.info("Old Anno: {}", oldAnno); + log.info("New Anno: {}", newAnno); + oldAnno.removeAll(newAnno); + + log.info("Old - New AV: {}", oldAnno); + + // If this is less than 0 + if (!oldAnno.isEmpty()) { + // Because of foreign keys -- clean up the Annotation Values + // This should cascade into AnnotationValue.points + avRepository.deleteByAnnotation_IdIn(oldAnno); + + } + + } + + /** + * Because of nested ids, recursively go through the annotations + children + * + * @param ids Set where ids should be added to + * @param annotation Parent annotation + * @return Ids of annotation and nested children + */ + public Set collectAnnotationIds(Set ids, Annotation annotation) { + if (annotation == null) { + return ids; + } + + ids.add(annotation.getId()); + for (Annotation child : annotation.getTempChildren()) { + collectAnnotationIds(ids, child); + } + + return ids; + } + +} \ No newline at end of file diff --git a/web/src/main/java/org/mitre/iwp/web/service/ReportResponseRepository.java b/web/src/main/java/org/mitre/iwp/web/service/ReportResponseRepository.java new file mode 100644 index 0000000..f13932b --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/ReportResponseRepository.java @@ -0,0 +1,18 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.ReportResponse; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ReportResponseRepository extends JpaRepository { + ReportResponse findReportResponseByReportDataId(long reportDataId); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/ReportService.java b/web/src/main/java/org/mitre/iwp/web/service/ReportService.java new file mode 100644 index 0000000..85c8641 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/ReportService.java @@ -0,0 +1,257 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import com.google.gson.Gson; +import com.itextpdf.text.DocumentException; + +import org.apache.commons.collections4.IterableUtils; +import org.apache.commons.lang3.StringUtils; +import org.mitre.iwp.web.exception.InvalidMessageException; +import org.mitre.iwp.web.model.*; +import org.mitre.iwp.web.reporting.ImageReporting; +import org.mitre.iwp.web.reporting.MultiModalReporting; +import org.mitre.iwp.web.reporting.SrbReporting; +import org.mitre.iwp.web.reporting.XmlReporting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import javax.xml.bind.DatatypeConverter; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.io.File; +import java.io.FileOutputStream; + +@Service +public class ReportService { + + private static final Logger log = LoggerFactory.getLogger(ReportService.class); + + private final ReportDataRepository reportRepository; + + private final ReportResponseRepository reportResponseRepository; + + private final AppNotificationRepository appNotificationRepository; + + private final PersonRepository personRepository; + + private final SrbReporting srbReporting; + + private final XmlReporting xmlReporting; + + private final ImageReporting imageReporting; + + private final MultiModalReporting multiModalReporting; + + private final String uploadPath; + + public ReportService(ReportDataRepository reportRepository, + ReportResponseRepository reportResponseRepository, + AppNotificationRepository appNotificationRepository, + PersonRepository personRepository, + SrbReporting srbReporting, + XmlReporting xmlReporting, + ImageReporting imageReporting, + MultiModalReporting multiModalReporting, + @Value("${iwp.upload.path}") String uploadPath) { + this.reportRepository = reportRepository; + this.reportResponseRepository = reportResponseRepository; + this.appNotificationRepository = appNotificationRepository; + this.personRepository = personRepository; + this.srbReporting = srbReporting; + this.xmlReporting = xmlReporting; + this.imageReporting = imageReporting; + this.multiModalReporting = multiModalReporting; + this.uploadPath = uploadPath; + } + + public ReportData getReportDataById(long id) { + Optional reportOption = this.reportRepository.findById(id); + if(reportOption.isPresent()) { + return reportOption.get(); + } + + return null; + } + + public ReportData getReportByFileName(String srbFileName) { + log.info("Getting ReportData for FileName: {}", srbFileName); + List returnList = this.reportRepository.findByFileName(srbFileName); + + log.info("{} Results returned", returnList.size()); + if (returnList.size() > 1) { + log.info("More than 1 report found, returning first item"); + log.info("{} Total reports with name: {}", returnList.size(), srbFileName); + } + + if (!returnList.isEmpty()) + return returnList.get(0); + + return null; + } + + public ReportData createPdfReport(ReportData reportData, UserAuthentication ua) { + AppNotification appNotification = new AppNotification(); + if( ua != null ) { + appNotification.setUserId(ua.getId()); + + if (reportData.getCreatedBy() == null || reportData.getCreatedBy().getId() == null) { + reportData.setCreatedBy(ua); + } + + reportData.setWorkedBy(ua); + } + + try { + this.storePdfFile(reportData); + + Long savedId = reportData.getId(); + log.info("Report Saved"); + + appNotification.setMessage("Review Report Ready for Download"); + appNotification.setDateCreated(Instant.now().toString()); + appNotification.setIsNew(true); + appNotification.setAction("/api/iris/report/download/" + savedId); + appNotification.setIcon("notifications"); + log.info("Trying to save App Notification"); + appNotificationRepository.save(appNotification); + log.info("App Notification Saved"); + if (savedId != null) { + log.info("pdf file ID is {}", savedId); + } else { + log.info("pdf file does not have ID"); + } + + + }catch(Exception e){ + appNotification.setMessage(String.format("Error Generating Report: %s", e.getMessage())); + appNotification.setDateCreated(Instant.now().toString()); + appNotification.setIsNew(true); + appNotification.setIcon("notifications"); + Gson gson = new Gson(); + log.info(gson.toJson(appNotification)); + log.info("Trying to save Erred App Notification"); + appNotificationRepository.save(appNotification); + log.info("Erred App Notification Saved"); + } + + return reportData; + } + + public void storePdfFile(ReportData reportData) throws DocumentException, InvalidMessageException, IOException, NoSuchAlgorithmException { + byte[] createdPdf = new byte[0]; + + if (!reportData.isValid()) { + String errorMessage = String.format("Error in Report Data: %s", + StringUtils.joinWith("\n\t", reportData.getErrorMessages())); + log.info(errorMessage); + } else { + log.info("Type: {}", reportData.getResponseType()); + + // Creating the PDF + if (reportData.getResponseType() == ResponseType.SRB) { + createdPdf = srbReporting.createPdfReport(reportData); + } else if (reportData.getResponseType() == ResponseType.XML) { + createdPdf = xmlReporting.createPdfReport(reportData); + } else if (reportData.getResponseType() == ResponseType.Unknown || reportData.getResponseType() == ResponseType.Image) { + if(reportData.getProbe().getImageList().size() > 1 || + reportData.getCandidates().get(0).getImageList().size() > 1) { + log.info("ReponseType: {}", reportData.getResponseType()); + createdPdf = multiModalReporting.createPdfReport(reportData); + } else { + log.info("ReponseType: {}", reportData.getResponseType()); + createdPdf = imageReporting.createPdfReport(reportData); + } + } else { + log.info("Unable to parse Response Type {}", reportData.getResponseType()); + } + + // Saving PDF to upload path, if the PDF was created successfully + if (createdPdf.length != 0) { + String hash = this.getHash(createdPdf); + String directory = uploadPath + "/" + hash.substring(0,2) + "/" + hash.substring(2,4); + String path = String.format("%s/%s.pdf", directory, hash); + + File f = new File(path); + log.info("File exists:{} path:{}", f.exists(), path); + + File dir = new File(directory); + if (!dir.mkdirs() && !dir.exists()) { + log.error("Unable to create the directory {}", directory); + throw new InvalidMessageException("Unable to create folders for upload"); + } + + ReportResponse reportResponse = null; + + if(reportData.getId() != null){ + reportResponse = this.reportResponseRepository.findReportResponseByReportDataId(reportData.getId()); + } + + if(reportResponse == null){ + reportResponse = new ReportResponse(); + reportResponse.setReportDataId(reportData.getId()); + reportResponse.setUserId(reportData.getUserId()); + } + reportResponse.setFilePath(path); + + this.reportResponseRepository.save(reportResponse); + try(FileOutputStream fos = new FileOutputStream(path)) { + log.info("Creating pdf file {}", path); + fos.write(createdPdf); + fos.flush(); + } catch (IOException e) { + log.error("Unable to create file {}", reportData.getFileName()); + throw new InvalidMessageException(e.getMessage()); + } + } + } + } + + public String getHash(byte[] byteArray) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(byteArray); + byte[] digest = md.digest(); + return DatatypeConverter.printHexBinary(digest).toUpperCase(); + } + + public void removeAllReports() { + reportRepository.deleteAll(); + } + + public Long savePerson(Person person) { + this.personRepository.save(person); + return person.getId(); + } + + public List listPersons() { + return IterableUtils.toList(this.personRepository.findAll()); + } + + public Person getPerson(long id) { + Optional personOptional = this.personRepository.findById(id); + return personOptional.orElse(null); + + } + + public void removeAllPersons() { + personRepository.deleteAll(); + } + + public void removePerson(long id) { + this.personRepository.deleteById(id); + } +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/ServiceResponseRepository.java b/web/src/main/java/org/mitre/iwp/web/service/ServiceResponseRepository.java new file mode 100644 index 0000000..7e6d43e --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/ServiceResponseRepository.java @@ -0,0 +1,19 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.ServiceResponse; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ServiceResponseRepository extends JpaRepository { +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/TshepiiCompareResponseRepository.java b/web/src/main/java/org/mitre/iwp/web/service/TshepiiCompareResponseRepository.java new file mode 100644 index 0000000..38abf64 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/TshepiiCompareResponseRepository.java @@ -0,0 +1,18 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.TshepiiCompareResponse; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TshepiiCompareResponseRepository extends JpaRepository { + TshepiiCompareResponse findByImageIdAndCandidateImageId(Long imageId, Long candidateImageId); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/TshepiiResponseRepository.java b/web/src/main/java/org/mitre/iwp/web/service/TshepiiResponseRepository.java new file mode 100644 index 0000000..e1007ff --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/TshepiiResponseRepository.java @@ -0,0 +1,20 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.TshepiiResponse; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TshepiiResponseRepository extends JpaRepository { + TshepiiResponse findByImageId(Long imageId ); +} diff --git a/web/src/main/java/org/mitre/iwp/web/service/UserAuthenticationRepository.java b/web/src/main/java/org/mitre/iwp/web/service/UserAuthenticationRepository.java new file mode 100644 index 0000000..44a4ade --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/UserAuthenticationRepository.java @@ -0,0 +1,32 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + + +import org.mitre.iwp.web.model.Profile; +import org.mitre.iwp.web.model.UserAuthentication; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +@Repository +public interface UserAuthenticationRepository extends JpaRepository { + UserAuthentication findByUsername(String username); + + @Modifying + @Query("delete from UserAuthentication u where u.profile = :profile") + @Transactional + void deleteByProfile(@Param("profile") Profile profile); +} + diff --git a/web/src/main/java/org/mitre/iwp/web/service/UserRoleRepository.java b/web/src/main/java/org/mitre/iwp/web/service/UserRoleRepository.java new file mode 100644 index 0000000..d6500c1 --- /dev/null +++ b/web/src/main/java/org/mitre/iwp/web/service/UserRoleRepository.java @@ -0,0 +1,25 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.service; + +import org.mitre.iwp.web.model.UserAuthentication; +import org.mitre.iwp.web.model.UserRole; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface UserRoleRepository extends JpaRepository { + UserRole findByName(String roleName); + List findByNameIn(List roleName); + List findByUsers(UserAuthentication user); +} diff --git a/web/src/main/resources/application.properties b/web/src/main/resources/application.properties new file mode 100644 index 0000000..b7a5774 --- /dev/null +++ b/web/src/main/resources/application.properties @@ -0,0 +1,207 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +# ActiveMQ services available +spring.activemq.broker-url=tcp://localhost:61616 +spring.activemq.user=${ARTEMIS_USER} +spring.activemq.password=${ARTEMIS_PASSWORD} + +iris.biqtService=true +iris.aciiService=true +iris.tshepiiService=true +iris.dualPDMService=true +iris.contactService=true +iris.irisAnnotationService=true +iris.pdmService=true + +iris.aciiQueueRequest=iris.acii.request +iris.aciiQueueResponse=iris.acii.response +iris.biqtQueueRequest=iris.biqt.request +iris.biqtQueueResponse=iris.biqt.response +iris.tshepiiQueueRequest=iris.tshepii.request +iris.tshepiiQueueResponse=iris.tshepii.response +iris.contactQueueResponse=iris.contactdetection.response +iris.irisAnnotationQueueRequest=iris.annotation.request +iris.irisAnnotationQueueResponse=iris.annotation.response +iris.pdmQueueRequest=iris.pdm.request +iris.pdmQueueResponse=iris.pdm.response +iris.dualPDMQueueRequest=iris.dual.pdm.request +iris.dualPDMQueueResponse=iris.dual.pdm.response + +iris.aciiServiceName=acii +iris.biqtServiceName=biqt +iris.tshepiiServiceName=tshepii +iris.dualPDMServiceName= dualpdm +iris.irisAnnotationServiceName=annotation +iris.pdmServiceName=pdm + +# Services addresses +iris.activeMqAddress=tcp://localhost:61616 +iris.iwpWebAddress=http://localhost:8080 +iris.activeMqHttpAddress=http://localhost:8161/console/jolokia/read/org.apache.activemq.artemis:broker=*,component=addresses,address=*,subcomponent=queues,*/ConsumerCount,MessagesAdded,MessagesAcknowledged +iris.activeMqUserName=${ARTEMIS_USER} +iris.activeMqPassword=${ARTEMIS_PASSWORD} + +#Spring Data +spring.jpa.hibernate.ddl-auto=update +spring.jpa.hibernate.use-new-id-generator-mappings=false +spring.datasource.url=jdbc:mysql://localhost:3306/${DATABASE_NAME}?allowPublicKeyRetrieval=TRUE&useSSL=FALSE +spring.datasource.username=${DATABASE_USERNAME} +spring.datasource.password=${DATABASE_PASSWORD} +spring.datasource.name=${DATABASE_NAME} +spring.jpa.properties.hibernate.jdbc.batch_size=100 +spring.jpa.properties.hibernate.order_inserts=true +spring.jpa.properties.hibernate.order_updates=true + +# Security Options +# Whether the application should respect @Secured annotations and use a login page +iwp.security.secured=true + +# The following user-password hashes are encrypted and decrypted at runtime with the configured Jasypt password. +# Use this command to decrypt a value with the standalone Jasypt tool. To encrypt a value, replace `./decrypt.sh` with `./encrypt.sh` and set `input` to the value to encrypt. +# The additional options are required because Spring Jasypt uses these defaults for decryption. +# When encrypting a hash on the command line, escape `$` characters with a `\`. +#./decrypt.sh input="STRINGTODECRYPT" password="$JASYPT_ENCRYPTOR_PASSWORD" algorithm=PBEWITHHMACSHA512ANDAES_256 verbose=true stringOutputType=base64 providerName=SunJCE saltGeneratorClassName=org.jasypt.salt.RandomSaltGenerator ivGeneratorClassName=org.jasypt.iv.RandomIvGenerator +#./encrypt.sh input="STRINGTOENCRYPT" password="$JASYPT_ENCRYPTOR_PASSWORD" algorithm=PBEWITHHMACSHA512ANDAES_256 verbose=true stringOutputType=base64 providerName=SunJCE saltGeneratorClassName=org.jasypt.salt.RandomSaltGenerator ivGeneratorClassName=org.jasypt.iv.RandomIvGenerator + +# Download the tools from http://www.jasypt.org/cli.html. If the ZIP download is unavailable, see https://github.com/jasypt/jasypt/releases. + +iwp.users.defaultAdminFullName=${DEFAULT_ADMIN_FULL_NAME} +iwp.users.defaultAdminTitle=${DEFAULT_ADMIN_TITLE} +iwp.users.defaultAdminEmail=${DEFAULT_ADMIN_EMAIL} +iwp.users.defaultAdminUsername=${DEFAULT_USER_USERNAME} +iwp.users.defaultAdminPassword=${DEFAULT_USER_PASSWORD} + +iwp.users.defaultReviewerFullName=${DEFAULT_REVIEWER_FULL_NAME} +iwp.users.defaultReviewerTitle=${DEFAULT_REVIEWER_TITLE} +iwp.users.defaultReviewerEmail=${DEFAULT_REVIEWER_EMAIL} +iwp.users.defaultReviewerUsername=${DEFAULT_REVIEWER_USERNAME} +iwp.users.defaultReviewerPassword=${DEFAULT_REVIEWER_PASSWORD} + +iwp.users.defaultSupervisorFullName=${DEFAULT_SUPERVISOR_FULL_NAME} +iwp.users.defaultSupervisorTitle=${DEFAULT_SUPERVISOR_TITLE} +iwp.users.defaultSupervisorEmail=${DEFAULT_SUPERVISOR_EMAIL} +iwp.users.defaultSupervisorUsername=${DEFAULT_SUPERVISOR_USERNAME} +iwp.users.defaultSupervisorPassword=${DEFAULT_SUPERVISOR_PASSWORD} + +#file Uploads +iwp.upload.path=/tmp +iwp.image.path=/tmp + +# +# Iris Annotation Properties +# +iris.annotations.0=Unknown +iris.annotations.1=Iris +iris.annotations.2=Eyelash +iris.annotations.3=Sclera +iris.annotations.4=Pupil +iris.annotations.5=Eyebrow +iris.annotations.6=Highlight +iris.annotations.7=Border +iris.annotations.8=Crypt +iris.annotations.9=Ridge +iris.annotations.10=Contact + +iris.annotationsColorHex.0=#CCCCCC +iris.annotationsColorHex.1=#0000FF +iris.annotationsColorHex.2=#FF0000 +iris.annotationsColorHex.3=#FFFFFF +iris.annotationsColorHex.4=#000000 +iris.annotationsColorHex.5=#00FFFF +iris.annotationsColorHex.6=#FFFF00 +iris.annotationsColorHex.7=#FF00FF +iris.annotationsColorHex.8=#33FFFF +iris.annotationsColorHex.9=#FF9933 +iris.annotationsColorHex.10=#00A5FF + +iris.adjudicationValue.0=Match,true +iris.adjudicationValue.1=Exclude,false +iris.adjudicationValue.2=Inconclusive,true + +# +# Versioning properties +# +iris.biqtServiceVersion=1.0 +iris.aciiServiceVersion=1.0 +iris.tshepiiServiceVersion=1.0 +iris.irisAnnotationServiceVersion=1.0 +iris.pdmServiceVersion=1.0 +iris.iwpBackendVersion=1.0 +iris.iwpFrontendVersion=1.0 + + +# Local Variables +iris.maxWidthImage=1000 +iris.maxHeightImage=1000 + +server.servlet.session.tracking-modes=cookie +server.error.include-exception=true +server.error.include-stacktrace=always +spring.main.allow-circular-references = true + +spring.servlet.multipart.enabled=true +spring.servlet.multipart.max-file-size=50MB +spring.servlet.multipart.max-request-size=50MB + +spring.jms.cache.session-cache-size=5 +spring.activemq.pool.enabled=true +spring.activemq.pool.max-connections=50 +spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER +log4j.logger.org.hibernate.SQL=debug +log4j.logger.org.hibernate.type.descriptor.sql=trace + +# Type 1 Record defaults +iris.ver=0050 +iris.tot=TOT +iris.pry=1 +iris.dai=DAI +iris.ori=IWPORI0000 +iris.tcn= +iris.tcr= +iris.nsr=00.00 +iris.ntr=00.00 +iris.dnm=NORAM +iris.dvn=EBTS 10.0 +iris.apo=APO +iris.apn=APN +iris.apv=1.0 + +# Type 2 Record defaults +iris.atn= +iris.cix= +iris.ncr=20 + +# Type 17 Record defaults +iris.cga=NONE + +# Profile defaults +iris.profileDai=TempDAI12 +iris.profileOri=TempORI12 +iris.profileAttIndicator=Attention +iris.profileSrcAgency=SRCAGENCY +iris.isProfileReportExclude=true +iris.profileDpt=DEPARTMENT +iris.profileAdd=ADDRESS +iris.profileStrokeWidth=2 +iris.profileDrawColor=#127bdc +iris.profileFillColor=#333333 +iris.profileNumberHistory=20 + +fileUploadTypesAllowed=image/jpg,image/png,image/jpeg,image/jp2,image/jp2l,image/bmp,.eft,.tiff,.sub,.ebts,.nist,.xml +maxNumUploadFiles=1000 + +validation.patterns.ebtsFile=^1\\.0{0,6}1: + +# +# USER NOTIFICATION PROPERTIES +# +notification.new.title=New Report +notification.new.message=A new report is available diff --git a/web/src/main/resources/xsd/dummy.xsd b/web/src/main/resources/xsd/dummy.xsd new file mode 100644 index 0000000..985ad12 --- /dev/null +++ b/web/src/main/resources/xsd/dummy.xsd @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/web/src/main/resources/xsd/mismatchRequest.xsd b/web/src/main/resources/xsd/mismatchRequest.xsd new file mode 100644 index 0000000..f9cd4e1 --- /dev/null +++ b/web/src/main/resources/xsd/mismatchRequest.xsd @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/test/java/org/mitre/iwp/web/data/MessagingTests.java b/web/src/test/java/org/mitre/iwp/web/data/MessagingTests.java new file mode 100644 index 0000000..ec74853 --- /dev/null +++ b/web/src/test/java/org/mitre/iwp/web/data/MessagingTests.java @@ -0,0 +1,384 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import java.io.File; +import java.io.IOException; +import java.math.BigInteger; +import java.net.URISyntaxException; +import java.net.URL; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.List; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; + +import org.apache.commons.io.IOUtils; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.runner.RunWith; +import org.mitre.iwp.mismatchrequest.DmeType; +import org.mitre.iwp.mismatchrequest.EncounterType; +import org.mitre.iwp.mismatchrequest.EncountersType; +import org.mitre.iwp.mismatchrequest.IdentityType; +import org.mitre.iwp.mismatchrequest.ImageType; +import org.mitre.iwp.mismatchrequest.ProbeType; +import org.mitre.iwp.mismatchrequest.Request; +import org.mitre.iwp.mismatchrequest.StatusType; +import org.mitre.iwp.mismatchrequest.TargetsType; +import org.mitre.iwp.web.data.config.AbstractInitializer; +import org.mitre.iwp.web.exception.InvalidMessageException; +import org.mitre.iwp.web.model.Candidate; +import org.mitre.iwp.web.model.EbtsMessageRequest; +import org.mitre.iwp.web.model.EyeLabel; +import org.mitre.iwp.web.model.ImageData; +import org.mitre.iwp.web.model.ReportData; +import org.mitre.iwp.web.model.ResponseType; +import org.mitre.iwp.web.service.IrisService; +import org.mitre.jet.ebts.Ebts; +import org.mitre.jet.ebts.EbtsBuilder; +import org.mitre.jet.ebts.EbtsParser; +import org.mitre.jet.ebts.field.Field; +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.mitre.jet.ebts.records.GenericRecord; +import org.mitre.jet.ebts.records.LogicalRecord; +import org.mitre.jet.exceptions.EbtsBuildingException; +import org.mitre.jet.exceptions.EbtsParsingException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.transaction.annotation.Transactional; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestPropertySource(locations = { "classpath:application-test.properties", + "classpath:application.properties" }, properties = { "iwp.security.secured=false", + "spring.main.allow-circular-references=true" }) +@Transactional +public class MessagingTests extends AbstractInitializer { + + @Autowired + private IrisService irisService; + + public void CreateXmlFile() throws JAXBException, IOException, URISyntaxException { + byte[] image1 = IOUtils.toByteArray(ClassLoader.getSystemResourceAsStream("Images/Image1.png")); + byte[] image2 = IOUtils.toByteArray(ClassLoader.getSystemResourceAsStream("Images/Image2.png")); + byte[] image3 = IOUtils.toByteArray(ClassLoader.getSystemResourceAsStream("Images/Image3.png")); + byte[] image4 = IOUtils.toByteArray(ClassLoader.getSystemResourceAsStream("Images/Image4.png")); + byte[] image5 = IOUtils.toByteArray(ClassLoader.getSystemResourceAsStream("Images/Image5.png")); + byte[] image6 = IOUtils.toByteArray(ClassLoader.getSystemResourceAsStream("Images/Image6.png")); + byte[] image7 = IOUtils.toByteArray(ClassLoader.getSystemResourceAsStream("Images/Image7.png")); + byte[] image8 = IOUtils.toByteArray(ClassLoader.getSystemResourceAsStream("Images/Image8.png")); + + Request req = new Request(); + req.setId(BigInteger.valueOf(10)); + + ProbeType probe = new ProbeType(); + probe.setUcn("ProbeUcn1"); + + EncounterType probeEncounter = new EncounterType(); + probeEncounter.setEcn("ECN17760704ProbeEcn"); + probeEncounter.setEncounterId("ProbeEncounter1"); + + ImageType imageType = new ImageType(); + imageType.setValue(image1); // image data + imageType.setElr(BigInteger.valueOf(11)); + imageType.setDme(DmeType.UC); + imageType.setStatus(StatusType.ENROLLED); + probeEncounter.getImages().add(imageType); + + imageType = new ImageType(); + imageType.setValue(image2); // image data + imageType.setElr(BigInteger.valueOf(11)); + imageType.setDme(DmeType.UC); + imageType.setStatus(StatusType.ENROLLED); + probeEncounter.getImages().add(imageType); + + probe.setEncounter(probeEncounter); + req.setProbe(probe); + + // add candidates + TargetsType targetsType = new TargetsType(); + targetsType.getTarget().add(CreateCandidate("1", image3, image4)); + targetsType.getTarget().add(CreateCandidate("2", image5, image6)); + targetsType.getTarget().add(CreateCandidate("3", image7, image8)); + + req.setTargets(targetsType); + + // write out xml + JAXBContext jaxbContext = JAXBContext.newInstance(Request.class); + Marshaller marshaller = jaxbContext.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + + URL resourceUrl = ClassLoader.getSystemResource("xmlExampleWithImages.xml"); + File file = new File(resourceUrl.toURI()); + marshaller.marshal(req, file); + } + + private IdentityType CreateCandidate(String candidateNumber, byte[] image1, byte[] image2) { + IdentityType candidate = new IdentityType(); + candidate.setUcn(String.format("CandidateUcn%s", candidateNumber)); + + EncountersType candidateEncounters = new EncountersType(); + + EncounterType encounter = new EncounterType(); + encounter.setEcn(String.format("EcnCandidate%s", candidateNumber)); + encounter.setEncounterId("EID00001"); + + ImageType imageType = new ImageType(); + imageType.setValue(image1); // image data + imageType.setElr(BigInteger.valueOf(11)); + imageType.setDme(DmeType.UC); + imageType.setStatus(StatusType.ENROLLED); + encounter.getImages().add(imageType); + + imageType = new ImageType(); + imageType.setValue(image2); // image data + imageType.setElr(BigInteger.valueOf(22)); + imageType.setDme(DmeType.UC); + imageType.setStatus(StatusType.ENROLLED); + encounter.getImages().add(imageType); + + candidateEncounters.getEncounter().add(encounter); + candidate.setEncounters(candidateEncounters); + + return candidate; + } + + @Test + public void TestSrbParsing() + throws IOException, EbtsParsingException, NoSuchAlgorithmException, EbtsBuildingException { + byte[] srbFile = createSrbFixture(loadTestImage()); + Ebts ebts = EbtsParser.parse(srbFile); + LogicalRecord type1 = ebts.getRecordsByType(1).get(0); + ReportData response = irisService.submitSrb(srbFile, 0l, ""); + + Assertions.assertNotNull(response.getProbe()); + Assertions.assertNotEquals(0, response.getCandidates().size()); + Assertions.assertEquals(ResponseType.SRB, response.getResponseType()); + + Assertions.assertEquals(response.getProbe().getTransactionControlNumber(), type1.getField(9).toString()); + Assertions.assertEquals(response.getProbe().getTransactionControlReference(), type1.getField(10).toString()); + + LogicalRecord type2 = ebts.getRecordsByType(2).get(0); + Field field = type2.getField(2033); + List candList = response.getCandidates(); + + Assertions.assertEquals(candList.size(), field.getOccurrences().size()); + + for (int i = 0; i < candList.size(); i++) { + Candidate candidate = candList.get(i); + Occurrence occurrence = field.getOccurrences().get(i); + + Assertions.assertEquals(candidate.getSubjectIdentifier(), occurrence.getSubFields().get(0).toString()); + Assertions.assertEquals(candidate.getScore(), occurrence.getSubFields().get(6).toString()); + } + } + + @Test + public void TestEbtsMessageCreation() + throws IOException, EbtsParsingException, InvalidMessageException, EbtsBuildingException { + EbtsMessageRequest ebtsReq = new EbtsMessageRequest(); + + String dateOfSubmission = "17760704"; + String destAgencyId = "dAgencyId"; + String origAgencyId = "oAgencyid"; + String tcn = "1234-1234-1234"; + + String cinPre = "cinPre"; + String cinId = "cinId"; + String attention = "Attention"; + + String sourceAgencyId = "sAgencyId"; + String irisCaptureDate = "17760704"; + int numbCandidates = 25; + EyeLabel elabel = EyeLabel.RIGHT; + + byte[] imageBytes = loadTestImage(); + + ebtsReq.setCinPrefix(cinPre); + ebtsReq.setCinIdentifier(cinId); + ebtsReq.setDateOfSubmission(dateOfSubmission); + ebtsReq.setDestinationAgencyIdentifier(destAgencyId); + ebtsReq.setOriginatingAgencyIdentifier(origAgencyId); + ebtsReq.setTransactionControlNumber(tcn); + ebtsReq.setAttentionIndicator(attention); + ebtsReq.setSourceAgency(sourceAgencyId); + ebtsReq.setIrisCaptureDate(irisCaptureDate); + ebtsReq.setNumberOfCandidates(numbCandidates); + ImageData data = new ImageData(); + data.setEyeLabel(elabel); + data.setImageBytes(imageBytes); + data.setImageData(Base64.getEncoder().encodeToString(imageBytes)); + data.setId(1L); + ebtsReq.setImageData(data); + + Ebts ebts = EbtsParser.parse(irisService.buildEbtsMessage(ebtsReq)); + + Assertions.assertEquals(1, ebts.getRecordsByType(1).size()); + LogicalRecord type1 = ebts.getRecordsByType(1).get(0); + + Assertions.assertEquals("EBTS", type1.getField(4).toString()); + Assertions.assertEquals(type1.getField(5).toString(), dateOfSubmission); + Assertions.assertEquals(type1.getField(7).toString(), destAgencyId); + Assertions.assertEquals(type1.getField(8).toString(), origAgencyId); + Assertions.assertEquals(type1.getField(9).toString(), tcn); + + Assertions.assertEquals(1, ebts.getRecordsByType(2).size()); + LogicalRecord type2 = ebts.getRecordsByType(2).get(0); + + Assertions.assertEquals(type2.getField(6).toString(), attention); + + Field field = type2.getField(10); + Occurrence occurrence = field.getOccurrences().get(0); + Assertions.assertEquals(occurrence.getSubFields().get(0).toString(), cinPre); + Assertions.assertEquals(occurrence.getSubFields().get(1).toString(), cinId); + + Assertions.assertEquals(1, ebts.getRecordsByType(17).size()); + LogicalRecord type17 = ebts.getRecordsByType(17).get(0); + + Assertions.assertEquals(type17.getField(4).toString(), sourceAgencyId); + Assertions.assertEquals(type17.getField(5).toString(), irisCaptureDate); + Assertions.assertEquals(type17.getField(3).toString(), Integer.toString(elabel.getValue())); + + byte[] ebtsImageBytes = type17.getImageData(); + + Assertions.assertArrayEquals(imageBytes, ebtsImageBytes); + } + + @Test + public void TestEbtsMessageValidation() throws IOException { + EbtsMessageRequest ebtsReq = new EbtsMessageRequest(); + + ebtsReq.setCinPrefix("cinPre"); + ebtsReq.setCinIdentifier("cinId"); + Assertions.assertFalse(ebtsReq.isValid()); + + ebtsReq.setDateOfSubmission("17760704"); + Assertions.assertFalse(ebtsReq.isValid()); + + ebtsReq.setDestinationAgencyIdentifier("123456789"); + Assertions.assertFalse(ebtsReq.isValid()); + + ebtsReq.setOriginatingAgencyIdentifier("123456789"); + Assertions.assertFalse(ebtsReq.isValid()); + + ebtsReq.setTransactionControlNumber("1234567890"); + Assertions.assertFalse(ebtsReq.isValid()); + + ebtsReq.setAttentionIndicator("Attention"); + Assertions.assertFalse(ebtsReq.isValid()); + + ebtsReq.setSourceAgency("srcAgency"); + Assertions.assertFalse(ebtsReq.isValid()); + + ebtsReq.setIrisCaptureDate("17760704"); + Assertions.assertFalse(ebtsReq.isValid()); + + ImageData imageData = new ImageData(); + byte[] imageBytes = loadTestImage(); + imageData.setImageBytes(imageBytes); + imageData.setImageData(Base64.getEncoder().encodeToString(imageBytes)); + imageData.setEyeLabel(EyeLabel.RIGHT); + ebtsReq.setImageData(imageData); + Assertions.assertFalse(ebtsReq.isValid()); + + ebtsReq.setNumberOfCandidates(20); + Assertions.assertTrue(ebtsReq.isValid()); + + ebtsReq.setNumberOfCandidates(19); + Assertions.assertFalse(ebtsReq.isValid()); + + ebtsReq.setNumberOfCandidates(51); + Assertions.assertFalse(ebtsReq.isValid()); + } + + private byte[] loadTestImage() throws IOException { + URL imageUrl = getClass().getClassLoader().getResource("Image.png"); + Assertions.assertNotNull(imageUrl, "Missing test resource Image.png"); + return IOUtils.toByteArray(imageUrl); + } + + private byte[] createSrbFixture(byte[] imageBytes) throws EbtsBuildingException { + Ebts ebts = new Ebts(); + + LogicalRecord type1 = new GenericRecord(1); + type1.setField(2, createField("0500")); + type1.setField(4, createField("SRB")); + type1.setField(5, createField("17760704")); + type1.setField(7, createField("SOURCEORI")); + type1.setField(8, createField("TARGETORI")); + type1.setField(9, createField("RESPONSE-1234")); + type1.setField(10, createField("TCN-123-456789")); + type1.setField(11, createField("00.00")); + type1.setField(12, createField("00.00")); + type1.setField(13, createField("NORAM", "EBTS 10.0")); + type1.setField(16, createField("APO", "APN", "1.0")); + ebts.addRecord(type1); + + Field candidateList = new Field(); + candidateList.getOccurrences().add(createOccurrence( + "SUBJECT1", "SMITH,JOHN", "4567", "11", "", "", "95", "40", "", "2", "", "", "", "12")); + candidateList.getOccurrences().add(createOccurrence( + "SUBJECT2", "JONES,JIM", "1234", "11", "", "", "75", "40", "", "3", "", "", "", "12")); + + LogicalRecord type2 = new GenericRecord(2); + type2.setField(6, createField("SMITH,JOHN")); + type2.setField(79, createField("2")); + type2.setField(2010, createField("50")); + type2.setField(2033, candidateList); + ebts.addRecord(type2); + + ebts.addRecord(createType17Record("1", "SOURCEORI", "17760704", imageBytes)); + ebts.addRecord(createType17Record("2", "SOURCEORI", "17760704", imageBytes)); + ebts.addRecord(createType17Record("3", "OTHERORI", "17760704", imageBytes)); + + return new EbtsBuilder().build(ebts); + } + + private LogicalRecord createType17Record(String idc, String sourceAgency, String captureDate, byte[] imageBytes) { + LogicalRecord type17 = new GenericRecord(17); + type17.setField(2, createField(idc)); + type17.setField(3, createField("1")); + type17.setField(4, createField(sourceAgency)); + type17.setField(5, createField(captureDate)); + type17.setField(6, createField("16")); + type17.setField(7, createField("16")); + type17.setField(8, createField("1")); + type17.setField(9, createField("16")); + type17.setField(10, createField("16")); + type17.setField(11, createField("PNG")); + type17.setField(12, createField("24")); + type17.setField(13, createField("GRAY")); + type17.setField(16, createField("0", "0", "0")); + type17.setField(19, createField("TEST", "MODEL", "SERIAL")); + type17.setImageData(imageBytes); + return type17; + } + + private Field createField(String... subfields) { + Field field = new Field(); + field.getOccurrences().add(createOccurrence(subfields)); + return field; + } + + private Occurrence createOccurrence(String... subfields) { + Occurrence occurrence = new Occurrence(); + for (String subfield : subfields) { + occurrence.getSubFields().add(new SubField(subfield)); + } + return occurrence; + } +} diff --git a/web/src/test/java/org/mitre/iwp/web/data/ProfileTests.java b/web/src/test/java/org/mitre/iwp/web/data/ProfileTests.java new file mode 100644 index 0000000..42952d9 --- /dev/null +++ b/web/src/test/java/org/mitre/iwp/web/data/ProfileTests.java @@ -0,0 +1,207 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.runner.RunWith; +import org.mitre.iwp.web.data.config.AbstractInitializer; +import org.mitre.iwp.web.exception.InvalidMessageException; +import org.mitre.iwp.web.model.Profile; +import org.mitre.iwp.web.service.IrisService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestPropertySource(locations = {"classpath:application-test.properties", "classpath:application.properties"}, + properties = { "iwp.security.secured=false", "spring.main.allow-circular-references=true" }) +@Transactional +public class ProfileTests extends AbstractInitializer { + + @Autowired + private IrisService irisService; + + Profile testProfile = null; + + @Test + public void TestProfiles() { + try{ + clearProfiles(); + } catch (Exception e) { + Assertions.assertTrue(false); // should not fail + } + + try { + ProfileTest(); + }catch(Exception e){ + Assertions.assertTrue(false); // should not fail + } + + try { + ProfileDuplicationTest(); + Assertions.assertTrue(false); // should fail + }catch(Exception e){ + Assertions.assertEquals("Profile name 'Profile 1' already exists in system", e.getMessage()); + } + + ProfileListTest(); + + try { + ProfileUpdateTest(); + }catch(Exception e){ + Assertions.assertTrue(false); // should not fail + } + + try { + ProfileUpdateFailureTest(); + Assertions.assertTrue(false); // should fail + } catch (Exception e) { + Assertions.assertEquals("Profile Id is empty", e.getMessage()); + } + + long idValue = Long.MIN_VALUE; + try { + ProfileDeleteFailureTest(idValue); + Assertions.assertTrue(false); // should fail + } catch (Exception e) { + Assertions.assertEquals(String.format("No profile found with id '%s'", idValue), e.getMessage()); + } + + try { + ProfileDeleteFailureTest2(); + Assertions.assertTrue(false); // should fail + } catch (Exception e) { + Assertions.assertEquals("Profile Id is empty", e.getMessage()); + } + + try { + ProfileDeleteTest(); + } catch (Exception e) { + Assertions.assertTrue(false); // should not fail + } + } + + private void clearProfiles() throws InvalidMessageException { + List profileList = irisService.listProfiles(); + for(Profile prof : profileList) { + irisService.deleteProfile(prof.getId()); + } + } + + private void ProfileDeleteFailureTest2() throws InvalidMessageException { + irisService.deleteProfile(-1); + } + + private void ProfileDeleteFailureTest(long idValue) throws InvalidMessageException { + irisService.deleteProfile(idValue); + } + + private void ProfileDeleteTest() throws InvalidMessageException { + int initialCount = irisService.listProfiles().size(); + irisService.deleteProfile(testProfile.getId()); + int finalCount = irisService.listProfiles().size(); + + Assertions.assertEquals(initialCount, finalCount + 1); + } + + public void ProfileTest() throws InvalidMessageException { + testProfile = new Profile(); + String name = "Profile 1"; + String sourceAgency = "srcAgency"; + String originAgency = "orgAgency"; + String destAgency = "desAgency"; + String attention = "attention"; + + testProfile.setName(name); + testProfile.setSourceAgency(sourceAgency); + testProfile.setIsReportExclude(true); + testProfile.setOriginatingAgencyIdentifier(originAgency); + testProfile.setDestinationAgencyIdentifier(destAgency); + testProfile.setAttentionIndicator(attention); + + irisService.createProfile(testProfile); + + Assertions.assertNotNull(testProfile.getId()); + } + + public void ProfileDuplicationTest() throws InvalidMessageException{ + // Assume ProfileTest() has run and a Profile named "Profile 1" exists in the repository + Profile profile = new Profile(); + String name = "Profile 1"; + String sourceAgency = "srcAgency"; + String originAgency = "orgAgency"; + String destAgency = "desAgency"; + String attention = "attention"; + + profile.setName(name); + profile.setSourceAgency(sourceAgency); + profile.setIsReportExclude(true); + profile.setOriginatingAgencyIdentifier(originAgency); + profile.setDestinationAgencyIdentifier(destAgency); + profile.setAttentionIndicator(attention); + + irisService.createProfile(profile); + } + + public void ProfileListTest() { + // Assume ProfileTest() ran successfully prior to this method + List profileList = irisService.listProfiles(); + Assertions.assertNotEquals(0, profileList.size()); + } + + public void ProfileUpdateTest() throws InvalidMessageException { + // Assume ProfileTest() ran successfully prior to this method + List profileList = irisService.listProfiles(); + + Profile profile = profileList.get(0); + String newName = profile.getName() + "Different Name"; + String oldName = profile.getName(); + long id = profile.getId(); + + profile.setName(newName); + irisService.updateProfile(profile); + + List newList = irisService.listProfiles(); + + Assertions.assertEquals(profileList.size(), newList.size()); + + for(Profile prof : newList){ + if(prof.getId() == id){ + Assertions.assertNotEquals(prof.getName(), oldName); + Assertions.assertEquals(prof.getName(), newName); + } + } + } + + public void ProfileUpdateFailureTest() throws InvalidMessageException { + Profile profile = new Profile(); + String name = "Profile 37"; + String sourceAgency = "Yep"; + String originAgency = "Nope"; + String destAgency = "Maybe"; + String attention = "Yo!"; + + profile.setName(name); + profile.setSourceAgency(sourceAgency); + profile.setIsReportExclude(true); + profile.setOriginatingAgencyIdentifier(originAgency); + profile.setDestinationAgencyIdentifier(destAgency); + profile.setAttentionIndicator(attention); + + irisService.updateProfile(profile); + } +} diff --git a/web/src/test/java/org/mitre/iwp/web/data/TypeCreationTests.java b/web/src/test/java/org/mitre/iwp/web/data/TypeCreationTests.java new file mode 100644 index 0000000..1ec06c1 --- /dev/null +++ b/web/src/test/java/org/mitre/iwp/web/data/TypeCreationTests.java @@ -0,0 +1,923 @@ +/* +* NOTICE +* +* This software (or technical data) was produced for the U. S. Government +* and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +* (May 2014) – Alternative IV (Dec 2007) +* +* (c) 2024 The MITRE Corporation. All Rights Reserved. +*/ + +package org.mitre.iwp.web.data; + +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.mitre.jet.ebts.Ebts; +import org.mitre.jet.ebts.field.Field; +import org.mitre.jet.ebts.field.Occurrence; +import org.mitre.jet.ebts.field.SubField; +import org.mitre.jet.ebts.records.GenericRecord; +import org.mitre.jet.ebts.records.LogicalRecord; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +public class TypeCreationTests { + + @Value("classpath:srb.eft") + private Resource srbResponse; + + @Test + public void TestType17Creation() { + Ebts ebts = new Ebts(); + + LogicalRecord record = new GenericRecord(17); + + // 17.002 - Image Designation Character + String idc = "01"; + record.setField(2, createField(idc)); + + // 17.003 - Eye Label + String elr = "1"; + record.setField(3, createField(elr)); + + // 17.004 - Source Agency + String src = "SAGENCY37"; + record.setField(4, createField(src)); + + // 17.005 - Iris Capture Date + String icd = "17760704"; + record.setField(5, createField(icd)); + + // 17.006 - Horizontal Line Length + String hll = "99999"; + record.setField(6, createField(hll)); + + // 17.007 - Vertical Line Length + String vll = "9999"; + record.setField(7, createField(vll)); + + // 17.008 - Scale Units + String slc = "1"; + record.setField(8, createField(slc)); + + // 17.009 - Transmitted Horizontal Pixel Scale + String thps = "1000"; + record.setField(9, createField(thps)); + + // 17.010 - Transmitted Vertical Pixel Scale + String tvps = "1000"; + record.setField(10, createField(tvps)); + + // 17.011 - Compression Algorithm + String cga = "NONE"; + record.setField(11, createField(cga)); + + // 17.012 - Bits Per Pixel + String bpx = "8"; + record.setField(12, createField(bpx)); + + // 17.013 - Color Space + String csp = "GRAY"; + record.setField(13, createField(csp)); + + // 17.014 - Rotation Angle of the Eye + String rae = "4000"; + record.setField(14, createField(rae)); + + // 17.015 - Rotation Uncertainty + String rau = "FFFF"; + record.setField(15, createField(rau)); + + // 17.016 - Image Property Code + String iho = "1"; + String ivo = "1"; + String ist = "1"; + record.setField(16, createField(iho, ivo, ist)); + + // 17.017 - Device Unique Identifier + String dui = "ALSDFOISDFLKWNEINDSLSDFSD"; + record.setField(17, createField(dui)); + + // 17.019 - Make Model Serial Number + String mak = "Generic Make"; + String mod = "Generic Model"; + String ser = "Generic Serial"; + record.setField(19, createField(mak, mod, ser)); + + // 17.020 - Eye Color + String ecl = "XXX"; + record.setField(20, createField(ecl)); + + // 17.021 - Comment + String com = "Free text field with commenting information, for the sake of commenting"; + record.setField(21, createField(com)); + + //17.025 - Effective Aquisition Spectrum + String eas = "NIR"; + record.setField(25, createField(eas)); + + // 17.028 - Damaged or Missing Eye + String dme = "UC"; + record.setField(28, createField(dme)); + + // 17.031 - Subject Acquisition Profile - Iris + String iap = "40"; + record.setField(31, createField(iap)); + + // 17.032 - Iris Storage Format + String isf = "1"; + record.setField(32, createField(isf)); + + ebts.addRecord(record); + List recordList = ebts.getRecordsByType(17); + + if(recordList.size() > 0) { + + LogicalRecord temp = recordList.get(0); + + Type17 t1 = Type17.buildFromEbtsRecord(temp); + Assertions.assertEquals(idc, t1.getImageDesignationCharacter()); + Assertions.assertEquals(Integer.parseInt(elr), t1.getEyeLabel()); + Assertions.assertEquals(src, t1.getSourceAgency()); + Assertions.assertEquals(icd, t1.getIrisCaptureDate()); + Assertions.assertEquals(Integer.parseInt(hll), t1.getHorizontalLineLength()); + Assertions.assertEquals(Integer.parseInt(vll), t1.getVerticalLineLength()); + Assertions.assertEquals(Integer.parseInt(slc), t1.getScaleUnits()); + Assertions.assertEquals(Integer.parseInt(thps), t1.getTransmittedHorizontalPixelScale()); + Assertions.assertEquals(Integer.parseInt(tvps), t1.getTransmittedVerticalPixelScale()); + Assertions.assertEquals(cga, t1.getCompressionAlgorithm()); + Assertions.assertEquals(Integer.parseInt(bpx), t1.getBitsPerPixel()); + Assertions.assertEquals(csp, t1.getColorSpace()); + Assertions.assertEquals(Integer.parseInt(rae), t1.getRotationAngleOfEye()); + Assertions.assertEquals(rau, t1.getRotationUncertainty()); + Assertions.assertEquals(Integer.parseInt(iho), t1.getImagePropertyCodeField().getHorizontalOrientationCode()); + Assertions.assertEquals(Integer.parseInt(ivo), t1.getImagePropertyCodeField().getVerticalOrientationCode()); + Assertions.assertEquals(Integer.parseInt(ist), t1.getImagePropertyCodeField().getSpecificScanType()); + Assertions.assertEquals(dui, t1.getDeviceUniqueIdentifier()); + Assertions.assertEquals(mak, t1.getMakeModelSerialNumberField().getMake()); + Assertions.assertEquals(mod, t1.getMakeModelSerialNumberField().getModel()); + Assertions.assertEquals(ser, t1.getMakeModelSerialNumberField().getSerialNumber()); + Assertions.assertEquals(ecl, t1.getEyeColor()); + Assertions.assertEquals(com, t1.getComment()); + Assertions.assertEquals(eas, t1.getEffectiveAcquisitionSpectrum()); + Assertions.assertEquals(Integer.parseInt(iap), t1.getSubjectAcquisitionProfileIris()); + Assertions.assertEquals(Integer.parseInt(isf), t1.getIrisStorageFormat()); + Assertions.assertEquals(dme, t1.getDamagedOrMissingEye()); + + /* + ****************************** + * + * + * Begin test conversion From Type 2 + * to Logical Record + * + * + * + ******************************/ + + LogicalRecord comparisonRecord = Type17.buildEbtsLogicalRecord(t1); + + Assertions.assertEquals(Integer.toString(t1.getEyeLabel()), comparisonRecord.getField(3).toString()); + Assertions.assertEquals(t1.getSourceAgency(), comparisonRecord.getField(4).toString()); + Assertions.assertEquals(t1.getIrisCaptureDate(), comparisonRecord.getField(5).toString()); + Assertions.assertEquals(Integer.toString(t1.getHorizontalLineLength()), comparisonRecord.getField(6).toString()); + Assertions.assertEquals(Integer.toString(t1.getVerticalLineLength()), comparisonRecord.getField(7).toString()); + Assertions.assertEquals(Integer.toString(t1.getScaleUnits()), comparisonRecord.getField(8).toString()); + Assertions.assertEquals(Integer.toString(t1.getTransmittedHorizontalPixelScale()), comparisonRecord.getField(9).toString()); + Assertions.assertEquals(Integer.toString(t1.getTransmittedVerticalPixelScale()), comparisonRecord.getField(10).toString()); + Assertions.assertEquals(t1.getCompressionAlgorithm(), comparisonRecord.getField(11).toString()); + Assertions.assertEquals(Integer.toString(t1.getBitsPerPixel()), comparisonRecord.getField(12).toString()); + Assertions.assertEquals(t1.getColorSpace(), comparisonRecord.getField(13).toString()); + Assertions.assertEquals(Integer.toString(t1.getRotationAngleOfEye()), comparisonRecord.getField(14).toString()); + Assertions.assertEquals(t1.getRotationUncertainty(), comparisonRecord.getField(15).toString()); + + Field tempField = comparisonRecord.getField(16); + Assertions.assertEquals(1, tempField.getOccurrences().size()); + Occurrence tempOcc = tempField.getOccurrences().get(0); + Assertions.assertEquals(3, tempOcc.getSubFields().size()); + Assertions.assertEquals(Integer.toString(t1.getImagePropertyCodeField().getHorizontalOrientationCode()), tempOcc.getSubFields().get(0).toString()); + Assertions.assertEquals(Integer.toString(t1.getImagePropertyCodeField().getVerticalOrientationCode()), tempOcc.getSubFields().get(1).toString()); + Assertions.assertEquals(Integer.toString(t1.getImagePropertyCodeField().getSpecificScanType()), tempOcc.getSubFields().get(2).toString()); + + Assertions.assertEquals(t1.getDeviceUniqueIdentifier(), comparisonRecord.getField(17).toString()); + + tempField = comparisonRecord.getField(19); + Assertions.assertEquals(1, tempField.getOccurrences().size()); + tempOcc = tempField.getOccurrences().get(0); + Assertions.assertEquals(3, tempOcc.getSubFields().size()); + Assertions.assertEquals(t1.getMakeModelSerialNumberField().getMake(), tempOcc.getSubFields().get(0).toString()); + Assertions.assertEquals(t1.getMakeModelSerialNumberField().getModel(), tempOcc.getSubFields().get(1).toString()); + Assertions.assertEquals(t1.getMakeModelSerialNumberField().getSerialNumber(), tempOcc.getSubFields().get(2).toString()); + + Assertions.assertEquals(t1.getEyeColor(), comparisonRecord.getField(20).toString()); + Assertions.assertEquals(t1.getComment(), comparisonRecord.getField(21).toString()); + Assertions.assertEquals(t1.getEffectiveAcquisitionSpectrum(), comparisonRecord.getField(25).toString()); + Assertions.assertEquals(Integer.toString(t1.getSubjectAcquisitionProfileIris()), comparisonRecord.getField(31).toString()); + Assertions.assertEquals(Integer.toString(t1.getIrisStorageFormat()), comparisonRecord.getField(32).toString()); + } + } + + @Test + public void TestType2Creation() { + Ebts ebts = new Ebts(); + + LogicalRecord record = new GenericRecord(2); + + // 2.002 - Image Designation Character + String idc = "01"; + record.setField(2, createField(idc)); + + // 2.006 - Attention Indicator + String atn = "Test"; + record.setField(6, createField(atn)); + + // 2.007 - Send Copy To + String sco1 = "WVCOPY123"; + String sco2 = "WVCOPY1234"; + String sco3 = "WVCOPY12345"; + record.setField(7, createFieldWithMultipleOccurences(sco1, sco2, sco3)); + + // 2.009 - Originating Agency Case Number + String oca = "AGENCY123"; + record.setField(9, createField(oca)); + + // 2.010 - Contributor Case Identifier Number + String cinPre = "Contributor Case Id"; + String cinId = "10"; + List occurenceList = new ArrayList<>(); + occurenceList.add(createOccurence(cinPre, cinId)); + occurenceList.add(createOccurence(cinPre, cinId)); + record.setField(10, createFieldFromOccurrences(occurenceList)); + + // 2.011 - Contributor Case ID Ext + String cix1 = "1"; + String cix2 = "2"; + String cix3 = "3"; + record.setField(11, createFieldWithMultipleOccurences(cix1, cix2, cix3)); + + // 2.014 - Universal Control Number + String ucn = "123456FH8"; + record.setField(14, createField(ucn)); + + // 2.015 - State ID + String sid1 = "WV12345678"; + String sid2 = "NC87654321"; + record.setField(15, createFieldWithMultipleOccurences(sid1, sid2)); + + // 2.017 - Miscellaneious Identification Number + String mnu1 = "MNU_VAL_1"; + String mnu2 = "MNU_VAL_2"; + record.setField(17, createFieldWithMultipleOccurences(mnu1, mnu2)); + + // 2.018 - Name + String nam = "Smith,John"; + record.setField(18, createField(nam)); + + // 2.020 - Place of Birth + String pob = "WV"; + record.setField(20, createField(pob)); + + // 2.021 - Citizenship + String ctz = "US"; + record.setField(21, createField(ctz)); + + // 2.022 - Date of Birth + String dob = "17760704"; + record.setField(22, createField(dob)); + + // 2.023 - Age Range + String agr = "2535"; + record.setField(23, createField(agr)); + + // 2.024 - Sex + String sex = "F"; + record.setField(24, createField(sex)); + + // 2.025 - Race + String rac = "U"; + record.setField(25, createField(rac)); + + // 2.026 - Scars Marks and Tattoos + String smt = "TAT ARM"; + record.setField(26, createField(smt)); + + // 2.028 - Height Range + String htr = "400711"; + record.setField(28, createField(htr)); + + // 2.030 - Weight Range + String wtr = "175185"; + record.setField(30, createField(wtr)); + + // 2.031 - Eye Color + String eye = "MAR"; + record.setField(31, createField(eye)); + + // 2.032 - Hair Color + String hai = "GRN"; + record.setField(32, createField(hai)); + + // 2.036 - Photo Available + String pht = "Y"; + record.setField(36, createField(pht)); + + // 2.038 - Date Printed + String dpr = "17760704"; + record.setField(38, createField(dpr)); + + // 2.059 - Search Result Findings + String srf = "Y"; + record.setField(59, createField(srf)); + + // 2.060 - Status Error Message + String msg1 = "Error Message Number 1"; + String msg2 = "Error Message, but not the same as message 1"; + record.setField(60, createFieldWithMultipleOccurences(msg1, msg2)); + + // 2.062 - Image Type + String imt1 = "image_11"; + String imt2 = "image_12"; + record.setField(62, createFieldWithMultipleOccurences(imt1, imt2)); + + // 2.073 - Controlling Agency Identifier + String cri1 = "AGENCY1"; + String cri2 = "AGENCY2"; + record.setField(73, createFieldWithMultipleOccurences(cri1, cri2)); + + // 2.079 - Number of Candidates Returned + String ncr = "7"; + record.setField(79, createField(ncr)); + + // 2.088 - NoteField + String not = "This is a note"; + record.setField(88, createField(not)); + + // 2.096 - Request Photo Record + String rpr = "Y"; + record.setField(96, createField(rpr)); + + // 2.2010 - Number of Images Requested + String nir = "37"; + record.setField(2010, createField(nir)); + + // 2.2023 - Supplementary Identity Information + String sii = "00 - Armed and Dangerous"; + record.setField(2023, createField(sii)); + + // 2.2028 - Biometric Image Description + String si = "123456HU8"; + String imt = "9"; + String bsi = "0123456789012345"; + String fnr = "32"; + String ppd = "23"; + String pos = "F"; + String elr = "3"; + occurenceList = new ArrayList<>(); + occurenceList.add(createOccurence(si, imt, bsi, fnr, ppd, pos, smt, elr)); + occurenceList.add(createOccurence(si, imt, bsi, fnr, ppd, pos, smt, elr)); + record.setField(2028, createFieldFromOccurrences(occurenceList)); + + // 2.2029 - Biometric Set Identifier + record.setField(2029, createField(bsi)); + + // 2.2031 - Biometric Image Available + String bia = "3"; + record.setField(2031, createField(bia)); + + // 2.2033 - Candidate Invastigative List + String fgp = "137"; + String msc = "1200"; + String ndr = "237"; + occurenceList = new ArrayList<>(); + occurenceList.add(createOccurence(si, nam, bsi, imt, fgp, ppd, msc, bia, ndr, idc, not, pos, smt, elr)); + occurenceList.add(createOccurence(si, nam, bsi, imt, fgp, ppd, msc, bia, ndr, idc, not, pos, smt, elr)); + occurenceList.add(createOccurence(si, nam, bsi, imt, fgp, ppd, msc, bia, ndr, idc, not, pos, smt, elr)); + record.setField(2033, createFieldFromOccurrences(occurenceList)); + + // 2.2035 - Event Identifier + String evi = ""; + record.setField(2035, createField(evi)); + + // 2.2061 - Biometric Image Enrollment + occurenceList = new ArrayList<>(); + occurenceList.add(createOccurence(bsi, imt, pos, smt)); + occurenceList.add(createOccurence(bsi, imt, pos, smt)); + record.setField(2061, createFieldFromOccurrences(occurenceList)); + + // 2.2073 - Biometric Image List + Timestamp bcd = Timestamp.valueOf("1776-07-04 01:02:03.123"); + occurenceList = new ArrayList<>(); + occurenceList.add(createOccurence(si, bsi, bcd.toString(), imt)); + occurenceList.add(createOccurence(si, bsi, bcd.toString(), imt)); + record.setField(2073, createFieldFromOccurrences(occurenceList)); + + ebts.addRecord(record); + List recordList = ebts.getRecordsByType(2); + + if(recordList.size() > 0) { + LogicalRecord temp = recordList.get(0); + + Type2 t2 = Type2.buildFromEbtsRecord(temp); + + Assertions.assertEquals(idc, t2.getInformationDesignationCharacter()); + Assertions.assertEquals(atn, t2.getAttentionIndicator()); + + List scoList = t2.getSendCopyTo(); + Assertions.assertTrue(scoList.contains(sco1)); + Assertions.assertTrue(scoList.contains(sco2)); + Assertions.assertTrue(scoList.contains(sco3)); + Assertions.assertEquals(3, scoList.size()); + + Assertions.assertEquals(oca, t2.getOriginatingAgencyCaseNumber()); + + List ccinList = t2.getContributorCaseIdentifierNumber(); + + for(ContributorCaseIdentifierNumberField ccin : ccinList){ + Assertions.assertEquals(cinPre, ccin.getContributorCasePrefix()); + Assertions.assertEquals(cinId, ccin.getContributorCaseId()); + } + Assertions.assertEquals(2, ccinList.size()); + + List cixList = t2.getContributorCaseIdExtension(); + Assertions.assertTrue(cixList.contains(Integer.parseInt(cix1))); + Assertions.assertTrue(cixList.contains(Integer.parseInt(cix2))); + Assertions.assertTrue(cixList.contains(Integer.parseInt(cix3))); + Assertions.assertEquals(3, cixList.size()); + + Assertions.assertEquals(ucn, t2.getUniversalControlNumber()); + + List sidList = t2.getStateIdentificationNumber(); + Assertions.assertTrue(sidList.contains(sid1)); + Assertions.assertTrue(sidList.contains(sid2)); + Assertions.assertEquals(2, sidList.size()); + + List mnuList = t2.getMiscellaneousIdentificationNumber(); + Assertions.assertTrue(mnuList.contains(mnu1)); + Assertions.assertTrue(mnuList.contains(mnu2)); + Assertions.assertEquals(2, mnuList.size()); + + Assertions.assertEquals(nam, t2.getName()); + Assertions.assertEquals(pob, t2.getPlaceOfBirth()); + Assertions.assertEquals(ctz, t2.getCitizenship()); + Assertions.assertEquals(dob, t2.getDateOfBirth()); + Assertions.assertEquals(Integer.parseInt(agr), t2.getAgeRange()); + Assertions.assertEquals(sex, t2.getSex()); + Assertions.assertEquals(rac, t2.getRace()); + Assertions.assertEquals(smt, t2.getScarsMarksAndTattoos()); + Assertions.assertEquals(Integer.parseInt(htr), t2.getHeightRange()); + Assertions.assertEquals(Integer.parseInt(wtr), t2.getWeightRange()); + Assertions.assertEquals(eye, t2.getEyeColor()); + Assertions.assertEquals(hai, t2.getHairColor()); + Assertions.assertEquals(pht, t2.getPhotoAvailableIndicator()); + Assertions.assertEquals(dpr, t2.getDatePrinted()); + Assertions.assertEquals(srf, t2.getSearchResultFindings()); + + List errorMessageList = t2.getStatusErrorMessage(); + Assertions.assertTrue(errorMessageList.contains(msg1)); + Assertions.assertTrue(errorMessageList.contains(msg2)); + Assertions.assertEquals(2, errorMessageList.size()); + + List imageTypeList = t2.getImageType(); + Assertions.assertTrue(imageTypeList.contains(imt1)); + Assertions.assertTrue(imageTypeList.contains(imt2)); + Assertions.assertEquals(2, imageTypeList.size()); + + + List controllingAgencyList = t2.getControllingAgencyIdentifier(); + Assertions.assertTrue(controllingAgencyList.contains(cri1)); + Assertions.assertTrue(controllingAgencyList.contains(cri2)); + Assertions.assertEquals(2, controllingAgencyList.size()); + + Assertions.assertEquals(Integer.parseInt(ncr), t2.getNumberOfCandidatesReturned()); + Assertions.assertEquals(not, t2.getNoteField()); + Assertions.assertEquals(rpr, t2.getRequestPhotoRecord()); + Assertions.assertEquals(Integer.parseInt(nir), t2.getNumberOfImagesRequested()); + Assertions.assertEquals(sii, t2.getSupplementaryIdentityInformation()); + + List bidList = t2.getBiometricImageDescription(); + for(BiometricImageDescriptionField bidField : bidList) { + Assertions.assertEquals(si, bidField.getSubjectIdentifier()); + Assertions.assertEquals(Integer.parseInt(imt), bidField.getImageType()); + Assertions.assertEquals(bsi, bidField.getBiometricSetIdentifier()); + Assertions.assertEquals(Integer.parseInt(fnr), bidField.getFingerNumberRequested()); + Assertions.assertEquals(ppd, bidField.getPrintPositionDescriptors()); + Assertions.assertEquals(pos, bidField.getSubjectPose()); + Assertions.assertEquals(smt, bidField.getNcicSmtCode()); + Assertions.assertEquals(Integer.parseInt(elr), bidField.getEyeLabel()); + } + + Assertions.assertEquals(2, bidList.size()); + Assertions.assertEquals(bsi, t2.getBiometricSetIdentifier()); + Assertions.assertEquals(Integer.parseInt(bia), t2.getBiometricImageAvailable()); + + List ciList = t2.getCandidateInvesitgativeListField(); + for(CandidateInvesitgativeListField ciField : ciList) { + Assertions.assertEquals(si, ciField.getSubjectIdentifier()); + Assertions.assertEquals(nam, ciField.getMasterName()); + Assertions.assertEquals(bsi, ciField.getBiometricSetIdentifier()); + Assertions.assertEquals(Integer.parseInt(imt), ciField.getImageType()); + Assertions.assertEquals(Integer.parseInt(fgp), ciField.getFrictionRidgeGeneralizedPosition()); + Assertions.assertEquals(ppd, ciField.getPrintPositionDescriptor()); + Assertions.assertEquals(Integer.parseInt(msc), ciField.getMatchScore()); + Assertions.assertEquals(Integer.parseInt(bia), ciField.getBiometricImageAvailable()); + Assertions.assertEquals(Integer.parseInt(ndr), ciField.getNameOfDesignatedRepository()); + Assertions.assertEquals(idc, ciField.getInformationDesignationCharacter()); + Assertions.assertEquals(not, ciField.getNoteField()); + Assertions.assertEquals(pos, ciField.getSubjectPose()); + Assertions.assertEquals(smt, ciField.getNcicSmtCode()); + Assertions.assertEquals(Integer.parseInt(elr), ciField.getEyeLabel()); + } + + Assertions.assertEquals(3, ciList.size()); + + Assertions.assertEquals(evi, t2.getEventIdentifier()); + + List bieList = t2.getBiometricImageEnrollment(); + for(BiometricImageEnrollmentField bie : bieList) + { + Assertions.assertEquals(bsi, bie.getBiometricSetIdentifier()); + Assertions.assertEquals(Integer.parseInt(imt), bie.getImageType()); + Assertions.assertEquals(pos, bie.getSubjectPose()); + Assertions.assertEquals(smt, bie.getScarsMarksTattoos()); + } + + Assertions.assertEquals(2, bieList.size()); + + List bilList = t2.getBiometricImageList(); + for(BiometricImageListField bil : bilList){ + Assertions.assertEquals(si, bil.getSubjectIdentifier()); + Assertions.assertEquals(bsi, bil.getBiometricSetIdentifier()); + Assertions.assertEquals(bcd, bil.getBiometricCaptureDate()); + Assertions.assertEquals(Integer.parseInt(imt), bil.getImageType()); + } + + Assertions.assertEquals(2, bilList.size()); + + /* + ****************************** + * + * Begin test conversion From Type 2 + * to Logical Record + * + * + * + ******************************/ + + LogicalRecord comparisonRecord = Type2.buildEbtsLogicalRecord(t2); + Assertions.assertEquals(t2.getAttentionIndicator(), comparisonRecord.getField(6).toString()); + + Field tempField = comparisonRecord.getField(7); + Assertions.assertEquals(3, tempField.getOccurrences().size()); + for (int i = 0; i < 3; i++) { + Assertions.assertEquals(t2.getSendCopyTo().get(i), tempField.getOccurrences().get(i).toString()); + } + + Assertions.assertEquals(t2.getOriginatingAgencyCaseNumber(), comparisonRecord.getField(9).toString()); + + tempField = comparisonRecord.getField(10); + Assertions.assertEquals(2, tempField.getOccurrences().size()); + Occurrence tempOccurrence = tempField.getOccurrences().get(0); + Assertions.assertEquals(2, tempOccurrence.getSubFields().size()); + Assertions.assertEquals(t2.getContributorCaseIdentifierNumber().get(0).getContributorCasePrefix(), tempOccurrence.getSubFields().get(0).toString()); + Assertions.assertEquals(t2.getContributorCaseIdentifierNumber().get(0).getContributorCaseId(), tempOccurrence.getSubFields().get(1).toString()); + tempOccurrence = tempField.getOccurrences().get(1); + Assertions.assertEquals(2, tempOccurrence.getSubFields().size()); + Assertions.assertEquals(t2.getContributorCaseIdentifierNumber().get(1).getContributorCasePrefix(), tempOccurrence.getSubFields().get(0).toString()); + Assertions.assertEquals(t2.getContributorCaseIdentifierNumber().get(1).getContributorCaseId(), tempOccurrence.getSubFields().get(1).toString()); + + tempField = comparisonRecord.getField(11); + Assertions.assertEquals(3, tempField.getOccurrences().size()); + for (int i = 0; i < 3; i++) { + Assertions.assertEquals(Integer.toString(t2.getContributorCaseIdExtension().get(i)), tempField.getOccurrences().get(i).toString()); + } + + Assertions.assertEquals(t2.getUniversalControlNumber(), comparisonRecord.getField(14).toString()); + + tempField = comparisonRecord.getField(15); + Assertions.assertEquals(2, tempField.getOccurrences().size()); + Assertions.assertEquals(t2.getStateIdentificationNumber().get(0), tempField.getOccurrences().get(0).toString()); + Assertions.assertEquals(t2.getStateIdentificationNumber().get(1), tempField.getOccurrences().get(1).toString()); + + tempField = comparisonRecord.getField(17); + Assertions.assertEquals(2, tempField.getOccurrences().size()); + Assertions.assertEquals(t2.getMiscellaneousIdentificationNumber().get(0), tempField.getOccurrences().get(0).toString()); + Assertions.assertEquals(t2.getMiscellaneousIdentificationNumber().get(1), tempField.getOccurrences().get(1).toString()); + + Assertions.assertEquals(t2.getName(), comparisonRecord.getField(18).toString()); + Assertions.assertEquals(t2.getPlaceOfBirth(), comparisonRecord.getField(20).toString()); + Assertions.assertEquals(t2.getCitizenship(), comparisonRecord.getField(21).toString()); + Assertions.assertEquals(t2.getDateOfBirth(), comparisonRecord.getField(22).toString()); + Assertions.assertEquals(Integer.toString(t2.getAgeRange()), comparisonRecord.getField(23).toString()); + Assertions.assertEquals(t2.getSex(), comparisonRecord.getField(24).toString()); + Assertions.assertEquals(t2.getRace(), comparisonRecord.getField(25).toString()); + Assertions.assertEquals(t2.getScarsMarksAndTattoos(), comparisonRecord.getField(26).toString()); + Assertions.assertEquals(Integer.toString(t2.getHeightRange()), comparisonRecord.getField(28).toString()); + Assertions.assertEquals(Integer.toString(t2.getWeightRange()), comparisonRecord.getField(30).toString()); + Assertions.assertEquals(t2.getEyeColor(), comparisonRecord.getField(31).toString()); + Assertions.assertEquals(t2.getHairColor(), comparisonRecord.getField(32).toString()); + Assertions.assertEquals(t2.getPhotoAvailableIndicator(), comparisonRecord.getField(36).toString()); + Assertions.assertEquals(t2.getDatePrinted(), comparisonRecord.getField(38).toString()); + Assertions.assertEquals(t2.getSearchResultFindings(), comparisonRecord.getField(59).toString()); + + tempField = comparisonRecord.getField(60); + Assertions.assertEquals(2, tempField.getOccurrences().size()); + Assertions.assertEquals(t2.getStatusErrorMessage().get(0), tempField.getOccurrences().get(0).toString()); + Assertions.assertEquals(t2.getStatusErrorMessage().get(1), tempField.getOccurrences().get(1).toString()); + + tempField = comparisonRecord.getField(62); + Assertions.assertEquals(2, tempField.getOccurrences().size()); + Assertions.assertEquals(t2.getImageType().get(0), tempField.getOccurrences().get(0).toString()); + Assertions.assertEquals(t2.getImageType().get(1), tempField.getOccurrences().get(1).toString()); + + tempField = comparisonRecord.getField(73); + Assertions.assertEquals(2, tempField.getOccurrences().size()); + Assertions.assertEquals(t2.getControllingAgencyIdentifier().get(0), tempField.getOccurrences().get(0).toString()); + Assertions.assertEquals(t2.getControllingAgencyIdentifier().get(1), tempField.getOccurrences().get(1).toString()); + + Assertions.assertEquals(Integer.toString(t2.getNumberOfCandidatesReturned()), comparisonRecord.getField(79).toString()); + Assertions.assertEquals(t2.getNoteField(), comparisonRecord.getField(88).toString()); + Assertions.assertEquals(t2.getRequestPhotoRecord(), comparisonRecord.getField(96).toString()); + Assertions.assertEquals(Integer.toString(t2.getNumberOfImagesRequested()), comparisonRecord.getField(2010).toString()); + Assertions.assertEquals(t2.getSupplementaryIdentityInformation(), comparisonRecord.getField(2023).toString()); + + tempField = comparisonRecord.getField(2028); + Assertions.assertEquals(2, tempField.getOccurrences().size()); + + for (int i = 0; i < 2; i++) { + tempOccurrence = tempField.getOccurrences().get(i); + Assertions.assertEquals(8, tempOccurrence.getSubFields().size()); + BiometricImageDescriptionField bid = t2.getBiometricImageDescription().get(i); + Assertions.assertEquals(bid.getSubjectIdentifier(), tempOccurrence.getSubFields().get(0).toString()); + Assertions.assertEquals(Integer.toString(bid.getImageType()), tempOccurrence.getSubFields().get(1).toString()); + Assertions.assertEquals(bid.getBiometricSetIdentifier(), tempOccurrence.getSubFields().get(2).toString()); + Assertions.assertEquals(Integer.toString(bid.getFingerNumberRequested()), tempOccurrence.getSubFields().get(3).toString()); + Assertions.assertEquals(bid.getPrintPositionDescriptors(), tempOccurrence.getSubFields().get(4).toString()); + Assertions.assertEquals(bid.getSubjectPose(), tempOccurrence.getSubFields().get(5).toString()); + Assertions.assertEquals(bid.getNcicSmtCode(), tempOccurrence.getSubFields().get(6).toString()); + Assertions.assertEquals(Integer.toString(bid.getEyeLabel()), tempOccurrence.getSubFields().get(7).toString()); + } + + Assertions.assertEquals(t2.getBiometricSetIdentifier(), comparisonRecord.getField(2029).toString()); + Assertions.assertEquals(Integer.toString(t2.getBiometricImageAvailable()), comparisonRecord.getField(2031).toString()); + + tempField = comparisonRecord.getField(2033); + Assertions.assertEquals(3, tempField.getOccurrences().size()); + + for (int i = 0; i < 3; i++) { + tempOccurrence = tempField.getOccurrences().get(i); + Assertions.assertEquals(14, tempOccurrence.getSubFields().size()); + CandidateInvesitgativeListField cil = t2.getCandidateInvesitgativeListField().get(i); + Assertions.assertEquals(cil.getSubjectIdentifier(), tempOccurrence.getSubFields().get(0).toString()); + Assertions.assertEquals(cil.getMasterName(), tempOccurrence.getSubFields().get(1).toString()); + Assertions.assertEquals(cil.getBiometricSetIdentifier(), tempOccurrence.getSubFields().get(2).toString()); + Assertions.assertEquals(Integer.toString(cil.getImageType()), tempOccurrence.getSubFields().get(3).toString()); + Assertions.assertEquals(Integer.toString(cil.getFrictionRidgeGeneralizedPosition()), tempOccurrence.getSubFields().get(4).toString()); + Assertions.assertEquals(cil.getPrintPositionDescriptor(), tempOccurrence.getSubFields().get(5).toString()); + Assertions.assertEquals(Integer.toString(cil.getMatchScore()), tempOccurrence.getSubFields().get(6).toString()); + Assertions.assertEquals(Integer.toString(cil.getBiometricImageAvailable()), tempOccurrence.getSubFields().get(7).toString()); + Assertions.assertEquals(Integer.toString(cil.getNameOfDesignatedRepository()), tempOccurrence.getSubFields().get(8).toString()); + Assertions.assertEquals(cil.getInformationDesignationCharacter(), tempOccurrence.getSubFields().get(9).toString()); + Assertions.assertEquals(cil.getNoteField(), tempOccurrence.getSubFields().get(10).toString()); + Assertions.assertEquals(cil.getSubjectPose(), tempOccurrence.getSubFields().get(11).toString()); + Assertions.assertEquals(cil.getNcicSmtCode(), tempOccurrence.getSubFields().get(12).toString()); + Assertions.assertEquals(Integer.toString(cil.getEyeLabel()), tempOccurrence.getSubFields().get(13).toString()); + } + + Assertions.assertEquals(t2.getEventIdentifier(), comparisonRecord.getField(2035).toString()); + + tempField = comparisonRecord.getField(2061); + Assertions.assertEquals(2, tempField.getOccurrences().size()); + + for (int i = 0; i < 2; i++) { + tempOccurrence = tempField.getOccurrences().get(i); + Assertions.assertEquals(4, tempOccurrence.getSubFields().size()); + BiometricImageEnrollmentField bie = t2.getBiometricImageEnrollment().get(i); + Assertions.assertEquals(bie.getBiometricSetIdentifier(), tempOccurrence.getSubFields().get(0).toString()); + Assertions.assertEquals(Integer.toString(bie.getImageType()), tempOccurrence.getSubFields().get(1).toString()); + Assertions.assertEquals(bie.getSubjectPose(), tempOccurrence.getSubFields().get(2).toString()); + Assertions.assertEquals(bie.getScarsMarksTattoos(), tempOccurrence.getSubFields().get(3).toString()); + } + + tempField = comparisonRecord.getField(2073); + Assertions.assertEquals(2, tempField.getOccurrences().size()); + + for (int i = 0; i < 2; i++) { + tempOccurrence = tempField.getOccurrences().get(i); + Assertions.assertEquals(4, tempOccurrence.getSubFields().size()); + BiometricImageListField bil = t2.getBiometricImageList().get(i); + Assertions.assertEquals(bil.getSubjectIdentifier(), tempOccurrence.getSubFields().get(0).toString()); + Assertions.assertEquals(bil.getBiometricSetIdentifier(), tempOccurrence.getSubFields().get(1).toString()); + Assertions.assertEquals(bil.getBiometricCaptureDate(), Timestamp.valueOf(tempOccurrence.getSubFields().get(2).toString())); + Assertions.assertEquals(Integer.toString(bil.getImageType()), tempOccurrence.getSubFields().get(3).toString()); + } + } + } + + @Test + public void TestType1Creation() { + Ebts ebts = new Ebts(); + + LogicalRecord record = new GenericRecord(1); + + // 1.002 - Version + String ver = "0500"; + record.setField(2, createField(ver)); + + // 1.003 - File Content + String frc = "1"; + String crc = "01"; + String rec = "02"; + String idc = "00"; + List occList = new ArrayList<>(); + occList.add(createOccurence(frc, crc)); + occList.add(createOccurence(rec, idc)); + record.setField(3, createFieldFromOccurrences(occList)); + + // 1.004 - Type of Transaction + String tot = "SRE"; + record.setField(4, createField(tot)); + + // 1.005 - Date + String dat = "17760704"; + record.setField(5, createField(dat)); + + // 1.006 - Transaction Priority + String pry = "1"; + record.setField(6, createField(pry)); + + // 1.007 - Destination Agency ID + String dai = "DESTAGC37"; + record.setField(7, createField(dai)); + + // 1.008 - Originating Agency ID + String ori = "WVUPUNT37"; + record.setField(8, createField(ori)); + + // 1.009 - Transaction Control Number + String tcn = "FAKE_TCN_FOR_TESTING_PURPOSES"; + record.setField(9, createField(tcn)); + + // 1.010 - Transaction Control Reference + String tcr = "NO_IDEA_WHAT_THIS_VALUE_SHOULD_BE"; + record.setField(10, createField(tcr)); + + // 1.011 - Native Scanning Resolution + String nsr = "00.00"; + record.setField(11, createField(nsr)); + + // 1.012 - Nominal Resolution + String ntr = "00.00"; + record.setField(12, createField(ntr)); + + // 1.013 - Domain Name + String dnm = "NORAM"; + String dvn = "EBTS 10.0"; + record.setField(13, createField(dnm, dvn)); + + // 1.016 - Application Profile Specifications + String apo = "APO"; + String apn = "APN"; + String apv = "1.0"; + record.setField(16, createField(apo, apn, apv)); + + ebts.addRecord(record); + List recordList = ebts.getRecordsByType(1); + + if(recordList.size() > 0){ + + LogicalRecord temp = recordList.get(0); + + Type1 t1 = Type1.buildFromEbtsRecord(temp); + + Assertions.assertEquals(ver, t1.getVersion()); + Assertions.assertEquals(tot, t1.getTypeOfTransaction()); + Assertions.assertEquals(Integer.parseInt(frc), t1.getFileContent().getFirstRecordCategoryCode()); + Assertions.assertEquals(crc, t1.getFileContent().getContentRecordCount()); + Assertions.assertEquals(rec, t1.getFileContent().getRecordCategoryCodes().get(0)); + Assertions.assertEquals(idc, t1.getFileContent().getInformationDesignationCharacters().get(0)); + Assertions.assertEquals(dat, t1.getDate()); + Assertions.assertEquals(pry, t1.getTransactionPriority()); + Assertions.assertEquals(dai, t1.getDestinationAgencyIdentifier()); + Assertions.assertEquals(ori, t1.getOriginatingAgencyIdentifier()); + Assertions.assertEquals(tcn, t1.getTransactionControlNumber()); + Assertions.assertEquals(tcr, t1.getTransactionControlReference()); + Assertions.assertEquals(nsr, t1.getNativeScanningResolution()); + Assertions.assertEquals(ntr, t1.getNominalResolution()); + Assertions.assertEquals(dnm, t1.getDomainName().getDomainName()); + Assertions.assertEquals(dvn, t1.getDomainName().getDomainVersionNumber()); + Assertions.assertEquals(apo, t1.getApplicationProfileSpecification().getApplicationProfileOrganization()); + Assertions.assertEquals(apn, t1.getApplicationProfileSpecification().getApplicationProfileName()); + Assertions.assertEquals(apv, t1.getApplicationProfileSpecification().getApplicationProfileVersionNumber()); + + /* + ****************************** + * + * + * Begin test conversion From Type 1 + * to Logical Record + * + * + * + ******************************/ + + LogicalRecord comparisonRecord = Type1.buildEbtsLogicalRecord(t1); + + Field tempField = comparisonRecord.getField(3); + Assertions.assertEquals(2, tempField.getOccurrences().size()); + Occurrence tempOcc = tempField.getOccurrences().get(0); + Assertions.assertEquals(2, tempOcc.getSubFields().size()); + Assertions.assertEquals(Integer.toString(t1.getFileContent().getFirstRecordCategoryCode()), tempOcc.getSubFields().get(0).toString()); + Assertions.assertEquals(t1.getFileContent().getContentRecordCount(), tempOcc.getSubFields().get(1).toString()); + tempOcc = tempField.getOccurrences().get(1); + Assertions.assertEquals(2, tempOcc.getSubFields().size()); + Assertions.assertEquals(t1.getFileContent().getRecordCategoryCodes().get(0), tempOcc.getSubFields().get(0).toString()); + Assertions.assertEquals(t1.getFileContent().getInformationDesignationCharacters().get(0), tempOcc.getSubFields().get(1).toString()); + + Assertions.assertEquals(t1.getTypeOfTransaction(), comparisonRecord.getField(4).toString()); + Assertions.assertEquals(t1.getDate(), comparisonRecord.getField(5).toString()); + Assertions.assertEquals(t1.getTransactionPriority(), comparisonRecord.getField(6).toString()); + Assertions.assertEquals(t1.getDestinationAgencyIdentifier(), comparisonRecord.getField(7).toString()); + Assertions.assertEquals(t1.getOriginatingAgencyIdentifier(), comparisonRecord.getField(8).toString()); + Assertions.assertEquals(t1.getTransactionControlNumber(), comparisonRecord.getField(9).toString()); + Assertions.assertEquals(t1.getTransactionControlReference(), comparisonRecord.getField(10).toString()); + + tempField = comparisonRecord.getField(13); + Assertions.assertEquals(1, tempField.getOccurrences().size()); + tempOcc = tempField.getOccurrences().get(0); + Assertions.assertEquals(2, tempOcc.getSubFields().size()); + Assertions.assertEquals(t1.getDomainName().getDomainName(), tempOcc.getSubFields().get(0).toString()); + Assertions.assertEquals(t1.getDomainName().getDomainVersionNumber(), tempOcc.getSubFields().get(1).toString()); + + tempField = comparisonRecord.getField(16); + Assertions.assertEquals(1, tempField.getOccurrences().size()); + tempOcc = tempField.getOccurrences().get(0); + Assertions.assertEquals(3, tempOcc.getSubFields().size()); + Assertions.assertEquals(t1.getApplicationProfileSpecification().getApplicationProfileOrganization(), tempOcc.getSubFields().get(0).toString()); + Assertions.assertEquals(t1.getApplicationProfileSpecification().getApplicationProfileName(), tempOcc.getSubFields().get(1).toString()); + Assertions.assertEquals(t1.getApplicationProfileSpecification().getApplicationProfileVersionNumber(), tempOcc.getSubFields().get(2).toString()); + } + + } + + private Field createField(String... dataArgs){ + Field field = new Field(); + Occurrence occurence = new Occurrence(); + List subList = new ArrayList<>(); + List occList = new ArrayList<>(); + + for(String data : dataArgs) { + SubField subField = new SubField(); + subField.setData(data); + subList.add(subField); + } + + occurence.setSubfields(subList); + occList.add(occurence); + + field.setOccurrences(occList); + + return field; + } + + private Field createFieldFromOccurrences(List occurenceList) { + Field field = new Field(); + field.setOccurrences(occurenceList); + return field; + } + + private Field createFieldWithMultipleOccurences(String... occurenceArgs){ + Field field = new Field(); + List occList = new ArrayList<>(); + + for(String data : occurenceArgs) { + Occurrence occurence = new Occurrence(); + SubField subField = new SubField(); + subField.setData(data); + + List subList = new ArrayList<>(); + subList.add(subField); + occurence.setSubfields(subList); + occList.add(occurence); + } + + field.setOccurrences(occList); + return field; + } + + private Occurrence createOccurence(String... occurrenceArgs){ + Occurrence occurrence = new Occurrence(); + List subList = new ArrayList<>(); + + for(String data : occurrenceArgs){ + SubField sf = new SubField(); + sf.setData(data); + subList.add(sf); + } + occurrence.setSubfields(subList); + return occurrence; + } +} diff --git a/web/src/test/java/org/mitre/iwp/web/data/config/AbstractInitializer.java b/web/src/test/java/org/mitre/iwp/web/data/config/AbstractInitializer.java new file mode 100644 index 0000000..cf3cd01 --- /dev/null +++ b/web/src/test/java/org/mitre/iwp/web/data/config/AbstractInitializer.java @@ -0,0 +1,117 @@ +package org.mitre.iwp.web.data.config; + +import java.io.IOException; +import java.util.Properties; + +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.io.support.PropertiesLoaderUtils; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.util.StringUtils; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.utility.DockerImageName; + +import jakarta.annotation.PreDestroy; + +public class AbstractInitializer { + + protected static final MySQLContainer mysql; + protected static final GenericContainer activeMQContainer; + private static final Properties APPLICATION_TEST_PROPERTIES = loadProperties("application-test.properties"); + private static final Properties APPLICATION_PROPERTIES = loadApplicationProperties(); + private static final StandardEnvironment ENVIRONMENT = createEnvironment(); + private static final String MYSQL_USERNAME = + getProperty("spring.datasource.username"); + private static final String MYSQL_PASSWORD = + getProperty("spring.datasource.password"); + private static final String MYSQL_NAME = + getProperty("spring.datasource.name"); + private static final String ACTIVE_MQ_USERNAME = + getProperty("spring.activemq.user", "iris.activeMqUserName"); + private static final String ACTIVE_MQ_PASSWORD = + getProperty("spring.activemq.password", "iris.activeMqPassword"); + + static { + // Starting the MySQL container as a singleton + mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8")) + .withExposedPorts(3306) + .withUsername(MYSQL_USERNAME) + .withPassword(MYSQL_PASSWORD) + .withDatabaseName(MYSQL_NAME); + mysql.start(); + + // Start ActiveMQ container + activeMQContainer = new GenericContainer<>( + DockerImageName.parse("dhi.io/activemq-artemis:2")) + .withExposedPorts(61616) + .withEnv("ARTEMIS_USER", ACTIVE_MQ_USERNAME) + .withEnv("ARTEMIS_PASSWORD", ACTIVE_MQ_PASSWORD); + activeMQContainer.start(); + } + + private static Properties loadApplicationProperties() { + return loadProperties("application.properties"); + } + + private static Properties loadProperties(String propertiesPath) { + try { + return PropertiesLoaderUtils.loadAllProperties(propertiesPath); + } catch (IOException e) { + throw new IllegalStateException("Could not load " + propertiesPath, e); + } + } + + private static StandardEnvironment createEnvironment() { + StandardEnvironment environment = new StandardEnvironment(); + environment.getPropertySources().addLast( + new PropertiesPropertySource("applicationTestProperties", APPLICATION_TEST_PROPERTIES)); + environment.getPropertySources().addLast( + new PropertiesPropertySource("applicationProperties", APPLICATION_PROPERTIES)); + return environment; + } + + private static String getProperty(String... propertyNames) { + for (String propertyName : propertyNames) { + String value = getOptionalProperty(propertyName); + if (StringUtils.hasText(value)) { + return value; + } + } + + throw new IllegalStateException( + "Missing required test configuration. Set one of: " + String.join(", ", propertyNames)); + } + + private static String getOptionalProperty(String propertyName) { + try { + return ENVIRONMENT.getProperty(propertyName); + } catch (IllegalArgumentException ignored) { + // Try the next configured key when a placeholder cannot be resolved. + return null; + } + } + + @PreDestroy + public void cleanup() { + mysql.close(); + activeMQContainer.close(); + } + + @DynamicPropertySource + static void registerMySQLProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", mysql::getJdbcUrl); + registry.add("spring.datasource.username", mysql::getUsername); + registry.add("spring.datasource.password", mysql::getPassword); + registry.add("spring.datasource.name", () -> MYSQL_NAME); + } + + @DynamicPropertySource + static void registerActiveMQProperties(DynamicPropertyRegistry registry) { + registry.add("spring.activemq.broker-url", () -> "tcp://" + activeMQContainer.getHost() + ":" + + activeMQContainer.getMappedPort(61616)); + registry.add("spring.activemq.user", () -> ACTIVE_MQ_USERNAME); + registry.add("spring.activemq.password", () -> ACTIVE_MQ_PASSWORD); + } +} diff --git a/web/src/test/resources/Image.png b/web/src/test/resources/Image.png new file mode 100644 index 0000000..b85e925 Binary files /dev/null and b/web/src/test/resources/Image.png differ diff --git a/web/src/test/resources/application-test.properties b/web/src/test/resources/application-test.properties new file mode 100644 index 0000000..a199d10 --- /dev/null +++ b/web/src/test/resources/application-test.properties @@ -0,0 +1,85 @@ +# +# NOTICE +# +# This software (or technical data) was produced for the U. S. Government +# and is subject to the Rights in Data-General Clause 52.227-14, Alt. IV +# (May 2014) – Alternative IV (Dec 2007) +# +# (c) 2024 The MITRE Corporation. All Rights Reserved. +# + +# Max sizes +spring.http.multipart.max-file-size=50MB +spring.http.multipart.max-request-size=50MB + +# TODO: Fill out these variables +# Requried Test Variables +spring.datasource.username=test +spring.datasource.password=test +spring.datasource.name=test +iris.activeMqUserName=test +iris.activeMqPassword=test + +# ActiveMQ services available +iris.biqtService=true +iris.aciiService=true +iris.dmipaService=false + +# Services addresses +iris.activeMqAddress=tcp://localhost:61616 +iris.iwpWebAddress=http://localhost:8080 + + +# Test users +iwp.users.defaultAdminFullName=Test Admin +iwp.users.defaultAdminTitle=Administrator +iwp.users.defaultAdminEmail=admin@example.test +iwp.users.defaultAdminUsername=admin +iwp.users.defaultAdminPassword=admin +iwp.users.defaultReviewerFullName=Test Reviewer +iwp.users.defaultReviewerTitle=Reviewer +iwp.users.defaultReviewerEmail=reviewer@example.test +iwp.users.defaultReviewerUsername=reviewer +iwp.users.defaultReviewerPassword=reviewer +iwp.users.defaultSupervisorFullName=Test Supervisor +iwp.users.defaultSupervisorTitle=Supervisor +iwp.users.defaultSupervisorEmail=supervisor@example.test +iwp.users.defaultSupervisorUsername=supervisor +iwp.users.defaultSupervisorPassword=supervisor + +# Type 1 Record defaults +default.ver=0050 +default.tot=TOT +default.pry=1 +default.dai=DAI +default.ori=IWPORI0000 +default.tcn= +default.tcr= +default.nsr=00.00 +default.ntr=00.00 +default.dnm=NORAM +default.dvn=EBTS 10.0 +default.apo=APO +default.apn=APN +default.apv=1.0 + +# Type 2 Record defaults +default.atn= +default.cix= +default.ncr=20 + +# Type 17 Record defaults +default.cga=NONE + +iwp.image.path=/tmp/images +iwp.upload.path=/tmp/SRBuploads +fileUploadTypesAllowed=image/jpg,image/png,image/jpeg,image/jp2,image/jp2l,image/bmp +maxNumUploadFiles=1000 + +validation.patterns.ebtsFile=^1\\.0{0,6}1: + +# +# USER NOTIFICATION PROPERTIES +# +notification.new.title=New Report +notification.new.message=A new report is available