├── .github └── CODEOWNERS ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── build.gradle.kts ├── djl-spring-boot-app ├── .gitignore ├── build.gradle.kts ├── gradle.properties ├── manifest.yml ├── settings.gradle.kts ├── siege-pcf.sh ├── siege.sh └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── aws │ │ │ └── samples │ │ │ └── djlspringboot │ │ │ ├── DjlSpringBootApplication.java │ │ │ ├── InferenceConfiguration.java │ │ │ ├── InferencePointController.java │ │ │ └── ObjectDetectionController.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── aws │ └── samples │ └── djlspringboot │ └── DjlSpringBootApplicationTests.java ├── djl-spring-boot-common ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── java │ └── com │ └── aws │ └── samples │ └── djl │ └── spring │ └── common │ ├── AmazonClientConfiguration.java │ ├── S3ImageDownloader.java │ └── S3ImageUploader.java ├── djl-spring-boot-model ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── java │ └── com │ └── aws │ └── samples │ └── djl │ └── spring │ └── model │ ├── InferenceResponse.java │ └── InferredObject.java ├── djl-spring-boot-web ├── .gitignore ├── README.md ├── build.gradle.kts ├── manifest.yml ├── settings.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── aws │ │ │ └── samples │ │ │ └── djldemoweb │ │ │ ├── DjlDemoWebApplication.kt │ │ │ ├── backend │ │ │ └── ObjectDetectionClient.kt │ │ │ ├── controller │ │ │ ├── IndexController.kt │ │ │ └── ObjectDetectionController.kt │ │ │ └── form │ │ │ └── ObjectDetectionForm.kt │ └── resources │ │ ├── application.properties │ │ ├── static │ │ └── style.css │ │ └── templates │ │ ├── index.html │ │ └── object-detection-files.html │ └── test │ └── kotlin │ └── com │ └── aws │ └── samples │ └── djldemoweb │ └── DjlDemoWebApplicationTests.kt ├── docs ├── AccessDjlBucketPolicyTemplate.json ├── deployment-template.yaml └── media │ └── djl-start-ide-support-low-frame-30s.gif ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Global Owners 2 | * @zachgk @frankfliu @DeepJavaLibrary/djl-admin 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/** 6 | !**/src/test/** 7 | 8 | AccessDjlBucketPolicy.json 9 | deployment.yaml 10 | kitten.jpg 11 | 12 | ### STS ### 13 | .apt_generated 14 | .classpath 15 | .factorypath 16 | .project 17 | .settings 18 | .springBeans 19 | .sts4-cache 20 | 21 | ### IntelliJ IDEA ### 22 | .idea 23 | *.iws 24 | *.iml 25 | *.ipr 26 | out/ 27 | 28 | ### NetBeans ### 29 | /nbproject/private/ 30 | /nbbuild/ 31 | /dist/ 32 | /nbdist/ 33 | /.nb-gradle/ 34 | 35 | ### VS Code ### 36 | .vscode/ 37 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DJL Spring Boot Demo 2 | 3 | This repository contains example code demonstrating how to use the [Deep Java Library](https://github.com/awslabs/djl) (DJL) with Spring Boot and the DJL Spring Boot Starter. 4 | It covers MXNet-based object detection inference with platform specific DJL libraries that can be consumed using DJL Spring Boot Starter dependencies. 5 | 6 | ## Building the backend 7 | Examples use Gradle as the build tool. It is a multi-project build. 8 | Before building, ensure that the DJL Spring Boot starter BOM is in your local maven repository. For more information, see the [DJL Spring Boot Starter repo](https://github.com/awslabs/djl-spring-boot-starter). You need to check out this repository and run `./mvnw install`. 9 | 10 | To build the DJL Spring Boot microservice, run the following command: 11 | 12 | ./gradlew :djl-spring-boot-app:bootJar 13 | 14 | This command detects the operating system on the system where the build is running and uses it for platform dependency resolution. 15 | 16 | Platform specific builds (for CI): 17 | 18 | ./gradlew :djl-spring-boot-app:bootJar -P osclassifier=linux-x86_64 19 | ./gradlew :djl-spring-boot-app:bootJar -P osclassifier=osx-x86_64 20 | ./gradlew :djl-spring-boot-app:bootJar -P osclassifier=win-x86_64 21 | 22 | The produced artifacts will have the classifier in the name of the spring boot uber jar, e.g. 23 | 24 | djl-spring-boot-app/build/libs/djl-spring-boot-app-0.0.1-SNAPSHOT-linux-x86_64.jar 25 | djl-spring-boot-app/build/libs/djl-spring-boot-app-0.0.1-SNAPSHOT-osx-x86_64.jar 26 | 27 | 28 | ## Running the backend 29 | 30 | Running the app requires the following: 31 | The S3 bucket (`djl-demo` by default) must be specified in `djl-spring-boot-app/src/main/resources/application.properties`. 32 | The bucket is expected to have two prefixes: `inbox`, where input is located, and `outbox`, where results are placed. 33 | `AWS_ACCESS_KEY` and `AWS_SECRET_KEY` set as environment variables for region `us-east-1`. 34 | The IAM user must have read permissions for the `inbox` and write permissions for the `outbox`. 35 | 36 | Run the following command to start the backend based on the created JAR file, sample command for macOS: 37 | 38 | java -jar djl-spring-boot-app/build/libs/djl-spring-boot-app-0.0.1-SNAPSHOT-osx-x86_64.jar 39 | 40 | Alternatively you can use Gradle for execution, sample command for Windows: 41 | 42 | gradlew :djl-spring-boot-app:bootRun -P osclassifier=win-x86_64 43 | 44 | ## Deploying to PCF 45 | 46 | Make sure your manifest.yml file contains valid S3 access credentials. You can specify your own bucket in [application.properties](djl-spring-boot-app/src/main/resources/application.properties) and [web application.properties](djl-spring-boot-web/src/main/resources/application.properties). 47 | 48 | Use the following command to deploy: 49 | 50 | cf login 51 | cf push -f manifest.yml -p build/libs/djl-spring-boot-app-0.0.1-SNAPSHOT-linux-x86_64.jar 52 | 53 | Check the logs: 54 | 55 | cd logs djl-demo --recent 56 | 57 | ## Deploying to EKS (Amazon Elastic Kubernetes Service) 58 | 59 | The instructions in this section may be generalized for any vanilla Kubernetes cluster, however they contain EKS 60 | specific features, such as IAM Roles integration for Kubernetes Service Accounts (IRSA). 61 | 62 | Deployment to EKS is implemented in such a way that credentials to access remote resources such as S3 buckets from 63 | the pod are not stored anywhere on the cluster, instead we will provision a role with the right IAM policy and use 64 | it instead. 65 | 66 | We will use [`eksctl` tool](https://eksctl.io/introduction/#installation) to provision an EKS cluster for 67 | demonstration purposes. 68 | You also need to install [kubectl](https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html) – A 69 | command line tool for working with Kubernetes clusters. 70 | 71 | Before you start, make sure you have AWS CLI installed and proper credentials to access your AWS account. 72 | Steps 2, 4 and 5 are only needed if you would like to protect access to your S3 bucket properly (it is NOT a good 73 | idea to open up an bucket with read/write access to the world). 74 | 75 | 1. Create EKS Cluster 76 | ```bash 77 | eksctl create cluster --name=djl-demo --nodes=2 78 | ``` 79 | 80 | 2. Create an OIDC Identity Provider (for IRSA) 81 | ``` 82 | eksctl utils associate-iam-oidc-provider --cluster djl-demo --approve 83 | ``` 84 | 85 | More info: [EKS Workshop](https://www.eksworkshop.com/beginner/110_irsa/oidc-provider/) 86 | 87 | 88 | 3. Create `djl-demo-` S3 bucket. We will use two prefixes: `inbox` for incoming images and `outbox` 89 | for outgoing processed images with detected objects. 90 | The bucket name must be unique so in this example we add a suffix for your AWS account number which should 91 | replace the placeholder . 92 | 93 | ```bash 94 | aws s3 mb s3://djl-demo- 95 | ``` 96 | 97 | Note: this will create a bucket that will require proper authorization. Objects in the bucket will not be accessible 98 | publicly. 99 | 100 | 101 | 4. Create IAM Policy to access the S3 bucket that you created (will be used for pods deployed in the demo): 102 | 103 | - Create a copy of the `docs/AccesDjlBucketPolicyTemplate.json` as `AccessDjlBucketPolicy.json` **replacing 104 | with your actual bucket name**. 105 | 106 | ```bash 107 | cp docs/AccessDjlBucketPolicyTemplate.json AccessDjlBucketPolicy.json 108 | # edit your AccessDjlBucketPolicy.json file 109 | aws iam create-policy --policy-name AccessDjlBucket --policy-document file://AccessDjlBucketPolicy.json 110 | ``` 111 | 112 | - You can get the ARN on of the created policy with the following command: 113 | 114 | ```bash 115 | aws iam list-policies --query 'Policies[?PolicyName==`AccessDjlBucket`].Arn' 116 | ``` 117 | 118 | 5. Create a service account for the backend service: 119 | 120 | **Note:** Adjust the namespace if you plan to deploy to a namespace different from default. 121 | 122 | ```bash 123 | eksctl create iamserviceaccount \ 124 | --name djl-backend-account \ 125 | --namespace default \ 126 | --cluster djl-demo \ 127 | --attach-policy-arn \ 128 | --approve \ 129 | --override-existing-serviceaccounts 130 | ``` 131 | 132 | 6. In this example we will use Amazon ECR private repository to push images. The repository will need to be created 133 | upfront. 134 | 135 | ```bash 136 | aws ecr create-repository --repository-name djl-spring-boot-app 137 | ``` 138 | 139 | 7. Build and push the container image for the API app to an accessible container (Docker) registry. 140 | 141 | - You must be properly authenticated to push images to Amazon ECR. For more info see this 142 | [doc](https://docs.aws.amazon.com/AmazonECR/latest/userguide/docker-push-ecr-image.html). 143 | 144 | ```bash 145 | aws ecr get-login-password --region | docker login --username AWS --password-stdin .dkr.ecr..amazonaws.com 146 | ``` 147 | 148 | - Assuming you forked this repository, modify the jib section of the `djl-spring-boot-app/build.gradle.kts` to 149 | reflect your settings (replace the placeholders): 150 | 151 | ```kotlin 152 | jib { 153 | from.image = "adoptopenjdk/openjdk13:debian" 154 | to.image = ".dkr.ecr..amazonaws.com/djl-spring-boot-app" 155 | to.tags = versionTags 156 | } 157 | ``` 158 | 159 | - From the root directory: 160 | 161 | ```bash 162 | ./gradlew djl-spring-boot-app:bootjar 163 | ./gradlew djl-spring-boot-app:jib 164 | ``` 165 | 166 | The above will push the image to the Amazon ECR container registry and output the image/tag pair that you will need 167 | for the subsequent steps. 168 | 169 | 170 | 8. Deploy the application: 171 | 172 | - Modify the provided `docs/deployment-template.yaml` and specify your image:tag produced in step 7 173 | 174 | - Run the command below to deploy the application and create a load balancer to access it over HTTP (don't use this 175 | approach in production without TLS in place): 176 | 177 | ```bash 178 | # assuming you saved the modified deployment-template.yaml in the current directory as deployment.yaml 179 | kubectl apply -f deployment.yaml 180 | ``` 181 | 182 | You can set the `-n YOUR_NAMESPACE` flag on the command if you created the service account in a different namespace. 183 | 184 | **Note:** this deployment is leveraging the service account `djl-backend-account` created in the previous steps. If you 185 | don't need a service account (e.g. in case you modified the app to read from local storage), then remove the 186 | service account association in the template (`serviceAccountName: djl-backend-account`). 187 | 188 | 189 | 9. Test your api: 190 | 191 | - Get the load balancer public DNS name by listing the created service 192 | 193 | ```bash 194 | # ensure the pod is in running state 195 | kubectl get po 196 | # get the loadbalancer URL 197 | kubectl get svc djl-app 198 | ``` 199 | 200 | - Upload a file to your S3 bucket `inbox` folder 201 | 202 | ```bash 203 | curl -O https://resources.djl.ai/images/kitten.jpg 204 | aws s3 cp kitten.jpg s3:///inbox/kitten.jpg 205 | ``` 206 | 207 | - Test with curl 208 | ```bash 209 | curl -v "http:///inference?file=kitten.jpg&generateOutputImage=true" 210 | ``` 211 | 212 | - Get the output file from s3 `outbox` folder (**at present .png extension is always appended**) 213 | 214 | ```bash 215 | aws s3 ls s3:///outbox/ 216 | aws s3 cp s3:///outbox/kitten.jpg.png 217 | ``` 218 | 219 | This completes the EKS deployment portion of the application. 220 | 221 | The EKS deployment of the API can be modified to scale up and down based on demand and spin up pods as needed based 222 | on HPA. The recommended approach to deploy any workloads on EKS is to use GipOps approach such as [FluxCD](https://fluxcd.io/) or [ArgoCD](https://argoproj.github.io/argo-cd/). 223 | 224 | ## Frontend Web Application 225 | [Web Application](djl-spring-boot-web/README.md) 226 | 227 | ## License 228 | This project is licensed under the Apache-2.0 License. 229 | 230 | 231 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | 3 | plugins { 4 | java 5 | id("org.springframework.boot") version "2.7.3" apply false 6 | id("io.spring.dependency-management") version "1.0.11.RELEASE" 7 | id ("com.google.cloud.tools.jib") version "3.1.1" apply false 8 | kotlin("jvm") version "1.5.20" apply false 9 | kotlin("plugin.spring") version "1.5.20" apply false 10 | } 11 | 12 | repositories { 13 | mavenCentral() 14 | } 15 | 16 | allprojects { 17 | ext.set("commitHash", getCommitHash()) 18 | } 19 | 20 | subprojects { 21 | apply(plugin = "io.spring.dependency-management") 22 | apply(plugin = "java") 23 | group = "com.aws.samples" 24 | version = "0.0.1-SNAPSHOT" 25 | 26 | repositories { 27 | mavenCentral() 28 | } 29 | 30 | java.sourceCompatibility=JavaVersion.VERSION_11 31 | java.targetCompatibility=JavaVersion.VERSION_11 32 | 33 | dependencyManagement { 34 | imports { 35 | mavenBom("org.springframework:spring-framework-bom:5.3.22") 36 | } 37 | } 38 | } 39 | 40 | fun getCommitHash() : String { 41 | return Runtime 42 | .getRuntime() 43 | .exec("git rev-parse --short HEAD") 44 | .let { process -> 45 | process.waitFor() 46 | val output : String = process.inputStream.use { 47 | it.bufferedReader().use(BufferedReader::readText) 48 | } 49 | process.destroy() 50 | output.trim() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /djl-spring-boot-app/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | .mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | .manifest.yml 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | 23 | ### NetBeans ### 24 | /nbproject/private/ 25 | /nbbuild/ 26 | /dist/ 27 | /nbdist/ 28 | /.nb-gradle/ 29 | build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | 34 | hs_err_pid* 35 | -------------------------------------------------------------------------------- /djl-spring-boot-app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.google.osdetector") version "1.6.2" 3 | id ("com.google.cloud.tools.jib") apply true 4 | id("org.springframework.boot") 5 | } 6 | 7 | repositories { 8 | mavenCentral() 9 | maven(url = "https://oss.sonatype.org/content/repositories/snapshots/") 10 | } 11 | 12 | val osclassifier : String? by project 13 | val inferredClassifier: String = osclassifier?: osdetector.classifier 14 | val timestamp = System.currentTimeMillis() 15 | val commitHash = ext.get("commitHash") 16 | val versionTags = generateVersionTag() 17 | 18 | jib { 19 | from.image = "adoptopenjdk/openjdk13:debian" 20 | to.image = "929819487611.dkr.ecr.us-east-1.amazonaws.com/djl-spring-boot-app" 21 | to.tags = versionTags 22 | } 23 | 24 | dependencies { 25 | implementation("org.springframework.boot:spring-boot-starter-web") 26 | //implementation("ai.djl.spring:djl-spring-boot-starter-mxnet-${inferredClassifier}:0.23") 27 | implementation("ai.djl.spring:djl-spring-boot-starter-pytorch-auto:0.23") 28 | implementation(project(":djl-spring-boot-common")) 29 | implementation(project(":djl-spring-boot-model")) 30 | implementation("org.springframework.boot:spring-boot-starter-actuator") 31 | testImplementation("org.springframework.boot:spring-boot-starter-test") { 32 | exclude(group = "org.junit.vintage", module = "junit-vintage-engine") 33 | } 34 | // See: https://github.com/deepjavalibrary/djl/blob/master/engines/mxnet/mxnet-engine/README.md for MXNet library selection 35 | } 36 | 37 | tasks.getByName("bootJar") { 38 | archiveClassifier.set(inferredClassifier) 39 | } 40 | 41 | tasks.withType { 42 | useJUnitPlatform() 43 | } 44 | 45 | fun generateVersionTag() : Set { 46 | project.logger.lifecycle("Version tag: ".plus(commitHash)) 47 | return setOf(version.toString().plus("-").plus(inferredClassifier).plus("-").plus(commitHash)) 48 | } 49 | 50 | -------------------------------------------------------------------------------- /djl-spring-boot-app/gradle.properties: -------------------------------------------------------------------------------- 1 | osclassifier=linux-x86_64 2 | #osclassifier=osx-x86_64 -------------------------------------------------------------------------------- /djl-spring-boot-app/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: djl-demo 4 | memory: 6G 5 | env: 6 | AWS_ACCESS_KEY: 7 | AWS_SECRET_KEY: 8 | JBP_CONFIG_OPEN_JDK_JRE: '{ jre: { version: 13.0.1_+ }, memory_calculator: { headroom: 50 }}' 9 | # headroom above is needed since the app is using JNA and allocates off heap memory 10 | -------------------------------------------------------------------------------- /djl-spring-boot-app/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "djl-spring-boot-app" -------------------------------------------------------------------------------- /djl-spring-boot-app/siege-pcf.sh: -------------------------------------------------------------------------------- 1 | siege -c 2 -t 1m -H "Content-Type: application/json" 'http://djl-demo.cfapps.io/inference?file=dog_bike_car.jpg' 2 | 3 | -------------------------------------------------------------------------------- /djl-spring-boot-app/siege.sh: -------------------------------------------------------------------------------- 1 | siege -c 11 -t 1m -H "Content-Type: application/json" 'http://localhost:8080/inference?file=dog_bike_car.jpg' 2 | 3 | -------------------------------------------------------------------------------- /djl-spring-boot-app/src/main/java/com/aws/samples/djlspringboot/DjlSpringBootApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://aws.amazon.com/apache2.0/ 8 | * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | package com.aws.samples.djlspringboot; 14 | 15 | import com.aws.samples.djl.spring.common.AmazonClientConfiguration; 16 | import org.springframework.boot.SpringApplication; 17 | import org.springframework.boot.autoconfigure.SpringBootApplication; 18 | import org.springframework.context.annotation.Import; 19 | 20 | @SpringBootApplication 21 | @Import(AmazonClientConfiguration.class) 22 | public class DjlSpringBootApplication { 23 | public static void main(String[] args) { 24 | SpringApplication.run(DjlSpringBootApplication.class, args); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /djl-spring-boot-app/src/main/java/com/aws/samples/djlspringboot/InferenceConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://aws.amazon.com/apache2.0/ 8 | * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | package com.aws.samples.djlspringboot; 14 | 15 | import ai.djl.Application; 16 | import ai.djl.MalformedModelException; 17 | import ai.djl.inference.Predictor; 18 | import ai.djl.modality.cv.Image; 19 | import ai.djl.modality.cv.ImageFactory; 20 | import ai.djl.modality.cv.output.DetectedObjects; 21 | import ai.djl.repository.zoo.Criteria; 22 | import ai.djl.repository.zoo.ModelNotFoundException; 23 | import ai.djl.repository.zoo.ModelZoo; 24 | import ai.djl.repository.zoo.ZooModel; 25 | import org.springframework.beans.factory.annotation.Qualifier; 26 | import org.springframework.context.annotation.Bean; 27 | import org.springframework.context.annotation.Configuration; 28 | import org.springframework.context.annotation.Scope; 29 | import org.springframework.context.annotation.ScopedProxyMode; 30 | 31 | import java.awt.image.BufferedImage; 32 | import java.io.IOException; 33 | import java.util.function.Supplier; 34 | 35 | @Configuration 36 | public class InferenceConfiguration { 37 | 38 | @Bean 39 | public ImageFactory imageFactory() { 40 | return ImageFactory.getInstance(); 41 | } 42 | 43 | @Bean 44 | public Criteria criteria() { 45 | return Criteria.builder() 46 | .setTypes(Image.class, DetectedObjects.class) 47 | .optApplication(Application.CV.OBJECT_DETECTION) 48 | // .optFilter("size", "512") 49 | // .optFilter("backbone", "mobilenet1.0") 50 | // .optFilter("dataset", "voc") 51 | .optArgument("threshold", 0.1) 52 | .build(); 53 | } 54 | 55 | @Bean 56 | public ZooModel model( 57 | @Qualifier("criteria") Criteria criteria) 58 | throws MalformedModelException, ModelNotFoundException, IOException { 59 | return ModelZoo.loadModel(criteria); 60 | } 61 | 62 | /** 63 | * Scoped proxy is one way to have a predictor configured and closed. 64 | * @param model object for which predictor is expected to be returned 65 | * @return predictor object that can be used for inference 66 | */ 67 | @Bean(destroyMethod = "close") 68 | @Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES) 69 | public Predictor predictor(ZooModel model) { 70 | return model.newPredictor(); 71 | } 72 | 73 | /** 74 | * Inject with @Resource or autowired. Only safe to be used in the try with resources. 75 | * @param model object for which predictor is expected to be returned 76 | * @return supplier of predictor for thread-safe inference 77 | */ 78 | @Bean 79 | public Supplier> predictorProvider(ZooModel model) { 80 | return model::newPredictor; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /djl-spring-boot-app/src/main/java/com/aws/samples/djlspringboot/InferencePointController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://aws.amazon.com/apache2.0/ 8 | * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | package com.aws.samples.djlspringboot; 14 | 15 | import ai.djl.inference.Predictor; 16 | import ai.djl.modality.cv.Image; 17 | import ai.djl.modality.cv.ImageFactory; 18 | import ai.djl.modality.cv.output.DetectedObjects; 19 | import ai.djl.translate.TranslateException; 20 | import com.aws.samples.djl.spring.common.S3ImageDownloader; 21 | import com.aws.samples.djl.spring.common.S3ImageUploader; 22 | import com.aws.samples.djl.spring.model.InferenceResponse; 23 | import com.aws.samples.djl.spring.model.InferredObject; 24 | 25 | import org.springframework.web.bind.annotation.GetMapping; 26 | import org.springframework.web.bind.annotation.RequestMapping; 27 | import org.springframework.web.bind.annotation.RequestParam; 28 | import org.springframework.web.bind.annotation.RestController; 29 | 30 | import javax.annotation.Resource; 31 | 32 | import java.awt.image.RenderedImage; 33 | import java.io.IOException; 34 | import java.util.LinkedList; 35 | import java.util.function.Supplier; 36 | 37 | @RestController 38 | public class InferencePointController { 39 | 40 | private static final String PNG = ".png"; 41 | 42 | @Resource 43 | private Supplier> predictorSupplier; 44 | 45 | @Resource 46 | private ImageFactory imageFactory; 47 | 48 | @Resource 49 | private S3ImageUploader uploader; 50 | 51 | @Resource 52 | private S3ImageDownloader downloader; 53 | 54 | @GetMapping 55 | @RequestMapping("/inference") 56 | public InferenceResponse detect(@RequestParam(name = "file") String fileName, 57 | @RequestParam(name = "generateOutputImage") Boolean generateOutputImage) 58 | throws IOException, TranslateException { 59 | 60 | Image image = imageFactory.fromInputStream(downloader.downloadStream(fileName)); 61 | var inferredObjects = new LinkedList(); 62 | 63 | var outputReference = ""; 64 | 65 | try(var p = predictorSupplier.get()) { 66 | var detected = p.predict(image); 67 | if(generateOutputImage != null && generateOutputImage) { 68 | RenderedImage newImage = createImage(detected, image); 69 | outputReference = uploader.upload(newImage, fileName.concat(PNG)); 70 | } 71 | detected.items().forEach(e -> inferredObjects.add(new InferredObject(e.getClassName(), e.getProbability()))); 72 | return new InferenceResponse(inferredObjects, outputReference); 73 | } 74 | } 75 | 76 | private static RenderedImage createImage(DetectedObjects detection, Image original) { 77 | Image newImage = original.duplicate(); 78 | newImage.drawBoundingBoxes(detection); 79 | return (RenderedImage) newImage.getWrappedImage(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /djl-spring-boot-app/src/main/java/com/aws/samples/djlspringboot/ObjectDetectionController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://aws.amazon.com/apache2.0/ 8 | * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | package com.aws.samples.djlspringboot; 14 | 15 | import org.springframework.stereotype.Controller; 16 | import org.springframework.ui.Model; 17 | import org.springframework.web.bind.annotation.GetMapping; 18 | import org.springframework.web.bind.annotation.RequestParam; 19 | 20 | //@TODO delete this 21 | @Controller 22 | public class ObjectDetectionController { 23 | 24 | @GetMapping("/object-detection") 25 | public String greeting(@RequestParam(name="name", required=false, defaultValue="World") String name, Model model) { 26 | model.addAttribute("name", name); 27 | return "object-detection"; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /djl-spring-boot-app/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | aws.s3.bucket=${DJL_S3_BUCKET} 2 | aws.s3.download-folder=inbox 3 | aws.s3.upload-folder=outbox 4 | 5 | logging.level.root=info 6 | logging.level.org.springframework=info 7 | 8 | info.app.name=djl-spring-boot-demo-api 9 | info.app.description=DJL Spring Boot Demo API with PyTorch 10 | info.app.version=0.0.1 11 | info.app.djl-boot-starter.version=0.6-SNAPSHOT 12 | -------------------------------------------------------------------------------- /djl-spring-boot-app/src/test/java/com/aws/samples/djlspringboot/DjlSpringBootApplicationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://aws.amazon.com/apache2.0/ 8 | * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | package com.aws.samples.djlspringboot; 14 | 15 | import org.junit.jupiter.api.Test; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | 18 | @SpringBootTest 19 | class DjlSpringBootApplicationTests { 20 | 21 | @Test 22 | void contextLoads() { 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /djl-spring-boot-common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | implementation("org.slf4j:slf4j-api:1.7.36") 4 | implementation("org.springframework:spring-core") 5 | implementation("org.springframework:spring-context") 6 | implementation("com.amazonaws:aws-java-sdk-s3:1.12.281") 7 | implementation("com.amazonaws:aws-java-sdk-sts:1.12.281") 8 | testImplementation("org.slf4j:slf4j-log4j12") 9 | } 10 | 11 | tasks.withType { 12 | useJUnitPlatform() 13 | } 14 | -------------------------------------------------------------------------------- /djl-spring-boot-common/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "djl-spring-boot-common" 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /djl-spring-boot-common/src/main/java/com/aws/samples/djl/spring/common/AmazonClientConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://aws.amazon.com/apache2.0/ 8 | * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | package com.aws.samples.djl.spring.common; 14 | 15 | import com.amazonaws.auth.AWSCredentialsProviderChain; 16 | import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; 17 | import com.amazonaws.auth.EnvironmentVariableCredentialsProvider; 18 | import com.amazonaws.auth.WebIdentityTokenCredentialsProvider; 19 | import com.amazonaws.auth.profile.ProfileCredentialsProvider; 20 | import com.amazonaws.regions.Regions; 21 | import com.amazonaws.services.s3.AmazonS3; 22 | import com.amazonaws.services.s3.AmazonS3ClientBuilder; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | import org.springframework.beans.factory.annotation.Value; 26 | import org.springframework.context.annotation.Bean; 27 | import org.springframework.context.annotation.Configuration; 28 | 29 | @Configuration 30 | /** 31 | * Consider spring cloud for aws as a potential option to configure. 32 | */ 33 | public class AmazonClientConfiguration { 34 | 35 | private static final Logger LOG = LoggerFactory.getLogger(AmazonClientConfiguration.class); 36 | 37 | @Value("${aws.s3.bucket}") 38 | private String bucketName; 39 | 40 | @Value("${aws.s3.upload-folder}") 41 | private String uploadFolder; 42 | 43 | @Value("${aws.s3.download-folder}") 44 | private String downloadFolder; 45 | 46 | 47 | @Bean 48 | public AmazonS3 s3() { 49 | LOG.info("Initializing aws provider with custom chain with WebIdentity provider"); 50 | return AmazonS3ClientBuilder.standard().withRegion(Regions.US_EAST_1) 51 | .withCredentials(new AWSCredentialsProviderChain(WebIdentityTokenCredentialsProvider.create(), 52 | new EnvironmentVariableCredentialsProvider(), 53 | new ProfileCredentialsProvider())) 54 | .build(); 55 | } 56 | 57 | @Bean 58 | public S3ImageDownloader downloader(AmazonS3 s3) { 59 | return new S3ImageDownloader(s3, bucketName, downloadFolder); 60 | } 61 | 62 | @Bean 63 | public S3ImageUploader uploader(AmazonS3 s3) { 64 | return new S3ImageUploader(s3, bucketName, uploadFolder); 65 | } 66 | } 67 | 68 | 69 | -------------------------------------------------------------------------------- /djl-spring-boot-common/src/main/java/com/aws/samples/djl/spring/common/S3ImageDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://aws.amazon.com/apache2.0/ 8 | * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | package com.aws.samples.djl.spring.common; 14 | 15 | import com.amazonaws.services.s3.AmazonS3; 16 | import com.amazonaws.services.s3.model.ObjectListing; 17 | import com.amazonaws.services.s3.model.S3ObjectInputStream; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | import java.awt.*; 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | 25 | public class S3ImageDownloader { 26 | 27 | private static final Logger LOG = LoggerFactory.getLogger(S3ImageDownloader.class); 28 | 29 | private AmazonS3 s3; 30 | 31 | private String bucketName; 32 | 33 | private String folder; 34 | 35 | public S3ImageDownloader(AmazonS3 s3, String bucketName, String folder) { 36 | this.s3 = s3; 37 | this.bucketName = bucketName; 38 | this.folder = folder == null ? "" : folder.concat("/"); 39 | } 40 | 41 | public InputStream downloadStream(String fileName) throws IOException { 42 | String key = fileName.contains("/")? fileName: folder.concat(fileName); 43 | LOG.info("Downloading {} from S3 bucket {}...\n", key, bucketName); 44 | return s3.getObject(bucketName, key).getObjectContent(); 45 | } 46 | 47 | public ObjectListing listFolder() { 48 | return s3.listObjects(bucketName, folder); 49 | } 50 | 51 | public ObjectListing listFolder(String folder) { 52 | return s3.listObjects(bucketName, folder); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /djl-spring-boot-common/src/main/java/com/aws/samples/djl/spring/common/S3ImageUploader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://aws.amazon.com/apache2.0/ 8 | * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | package com.aws.samples.djl.spring.common; 14 | 15 | import com.amazonaws.services.s3.AmazonS3; 16 | import com.amazonaws.services.s3.model.ObjectMetadata; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import javax.imageio.ImageIO; 21 | import java.awt.image.RenderedImage; 22 | import java.io.ByteArrayInputStream; 23 | import java.io.ByteArrayOutputStream; 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | 27 | public class S3ImageUploader { 28 | private static final Logger LOG = LoggerFactory.getLogger(S3ImageDownloader.class); 29 | 30 | private AmazonS3 s3; 31 | 32 | private String bucketName; 33 | 34 | private String folder; 35 | 36 | private static final String S3REF = "https://%s.s3.amazonaws.com/%s"; 37 | 38 | 39 | public S3ImageUploader(AmazonS3 s3, String bucketName, String folder) { 40 | this.s3 = s3; 41 | this.bucketName = bucketName; 42 | this.folder = folder == null ? "" : folder.concat("/"); 43 | } 44 | 45 | public String upload(RenderedImage image, String file) throws IOException { 46 | ByteArrayOutputStream os = new ByteArrayOutputStream(); 47 | ImageIO.write(image, "png", os); 48 | byte[] buf = os.toByteArray(); 49 | try(var is = new ByteArrayInputStream(buf)) { 50 | return upload(is, buf.length, file); 51 | } 52 | } 53 | 54 | public String upload(InputStream inputStream, long contentLength, String file) throws IOException { 55 | String key = folder.concat(file); 56 | LOG.info("Uploading {} to S3 bucket {}...\n", key, bucketName); 57 | ObjectMetadata metadata = new ObjectMetadata(); 58 | metadata.setContentLength(contentLength); 59 | // byte[] resultByte = DigestUtils.md5(inputStream); 60 | // String streamMD5 = new String(Base64.encodeBase64(resultByte)); 61 | // metadata.setContentMD5(streamMD5); 62 | s3.putObject(bucketName, key, inputStream, metadata); 63 | return String.format(S3REF, bucketName, key); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /djl-spring-boot-model/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | implementation("jakarta.validation:jakarta.validation-api:2.0.2") 4 | } 5 | 6 | tasks.withType { 7 | useJUnitPlatform() 8 | } 9 | -------------------------------------------------------------------------------- /djl-spring-boot-model/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "djl-spring-boot-model" 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /djl-spring-boot-model/src/main/java/com/aws/samples/djl/spring/model/InferenceResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://aws.amazon.com/apache2.0/ 8 | * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | package com.aws.samples.djl.spring.model; 14 | 15 | import javax.validation.constraints.NotNull; 16 | import java.util.List; 17 | 18 | public class InferenceResponse { 19 | 20 | private String outputReference; 21 | 22 | private List inferredObjects; 23 | 24 | /** 25 | * Deserialization constructor 26 | */ 27 | public InferenceResponse(){} 28 | 29 | public InferenceResponse(@NotNull List inferredObjects, 30 | String outputReference) { 31 | this.inferredObjects = inferredObjects; 32 | this.outputReference = outputReference; 33 | } 34 | 35 | public List getInferredObjects() { 36 | return inferredObjects; 37 | } 38 | 39 | public void setInferredObjects(List inferredObjects) { 40 | this.inferredObjects = inferredObjects; 41 | } 42 | 43 | public String getOutputReference() { 44 | return outputReference; 45 | } 46 | 47 | public void setOutputReference(String outputReference) { 48 | this.outputReference = outputReference; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /djl-spring-boot-model/src/main/java/com/aws/samples/djl/spring/model/InferredObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://aws.amazon.com/apache2.0/ 8 | * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | package com.aws.samples.djl.spring.model; 14 | 15 | public class InferredObject { 16 | 17 | private String objectClass; 18 | 19 | private Double probability; 20 | 21 | public InferredObject(){} 22 | 23 | public InferredObject(String objectClass, Double probability) { 24 | this.objectClass = objectClass; 25 | this.probability = probability; 26 | } 27 | 28 | public String getObjectClass() { 29 | return objectClass; 30 | } 31 | 32 | public Double getProbability() { 33 | return probability; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /djl-spring-boot-web/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/** 6 | !**/src/test/** 7 | .manifest.yml 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | 18 | ### IntelliJ IDEA ### 19 | .idea 20 | *.iws 21 | *.iml 22 | *.ipr 23 | out/ 24 | 25 | ### NetBeans ### 26 | /nbproject/private/ 27 | /nbbuild/ 28 | /dist/ 29 | /nbdist/ 30 | /.nb-gradle/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /djl-spring-boot-web/README.md: -------------------------------------------------------------------------------- 1 | # DJL Demo Web Application 2 | To run the web application directly execute: 3 | 4 | gradlew :djl-spring-boot-web:bootRun 5 | 6 | To deploy to PCF, edit `manifest.yml` and supply the access key and secret key that allow access to the desired S3 bucket. 7 | 8 | The bucket name is set to `djl-demo` in application.properties file. 9 | 10 | The bucket name must the same as the one used by the `djl-spring-boot-app` project, which acts as the backend. 11 | -------------------------------------------------------------------------------- /djl-spring-boot-web/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.io.* 2 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 3 | 4 | plugins { 5 | id("org.springframework.boot") 6 | id ("com.google.cloud.tools.jib") apply true 7 | kotlin("jvm") 8 | kotlin("plugin.spring") 9 | } 10 | val commitHash = ext.get("commitHash") 11 | 12 | jib { 13 | from.image = "openjdk:13" 14 | to.image = "929819487611.dkr.ecr.us-east-1.amazonaws.com/djl-spring-boot-web" 15 | to.tags = setOf(version.toString().plus("-").plus(commitHash)) 16 | } 17 | 18 | dependencies { 19 | implementation("org.springframework.boot:spring-boot-starter-actuator") 20 | implementation("org.springframework.boot:spring-boot-starter-thymeleaf") 21 | implementation("org.springframework.boot:spring-boot-starter-webflux") 22 | implementation("org.springframework.boot:spring-boot-starter-web") 23 | implementation("com.fasterxml.jackson.module:jackson-module-kotlin") 24 | implementation(project(":djl-spring-boot-common")) 25 | implementation("com.amazonaws:aws-java-sdk-s3:1.12.281") // TODO: this was not resolved through transitive dependency on djl-sping-boot-common 26 | implementation(project(":djl-spring-boot-model")) 27 | implementation("org.jetbrains.kotlin:kotlin-reflect") 28 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") 29 | testImplementation("org.springframework.boot:spring-boot-starter-test") { 30 | exclude(group = "org.junit.vintage", module = "junit-vintage-engine") 31 | } 32 | } 33 | 34 | tasks.withType { 35 | useJUnitPlatform() 36 | } 37 | 38 | tasks.withType { 39 | kotlinOptions { 40 | freeCompilerArgs = listOf("-Xjsr305=strict") 41 | jvmTarget = "11" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /djl-spring-boot-web/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: djl-demo-web 4 | memory: 700M 5 | env: 6 | AWS_ACCESS_KEY: <> 7 | AWS_SECRET_KEY: <> 8 | JBP_CONFIG_OPEN_JDK_JRE: '{ jre: { version: 13.0.1_+ } }' 9 | DJL_APP_URL: http://djl-demo.cfapps.io 10 | -------------------------------------------------------------------------------- /djl-spring-boot-web/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "djl-spring-boot-web" 2 | -------------------------------------------------------------------------------- /djl-spring-boot-web/src/main/kotlin/com/aws/samples/djldemoweb/DjlDemoWebApplication.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://aws.amazon.com/apache2.0/ 8 | * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | package com.aws.samples.djldemoweb 14 | 15 | import com.aws.samples.djl.spring.common.AmazonClientConfiguration 16 | import org.slf4j.Logger 17 | import org.slf4j.LoggerFactory 18 | import org.springframework.beans.factory.annotation.Value 19 | import org.springframework.boot.autoconfigure.SpringBootApplication 20 | import org.springframework.boot.runApplication 21 | import org.springframework.context.annotation.Bean 22 | import org.springframework.context.annotation.Import 23 | import org.springframework.http.HttpHeaders 24 | import org.springframework.http.MediaType 25 | import org.springframework.web.reactive.function.client.WebClient 26 | 27 | @SpringBootApplication 28 | @Import(AmazonClientConfiguration::class) 29 | class DjlDemoWebApplication { 30 | 31 | companion object { 32 | val LOG: Logger = LoggerFactory.getLogger(DjlDemoWebApplication::class.java) 33 | } 34 | 35 | @Value("\${djl.app.url}") 36 | lateinit var apiUrl: String 37 | 38 | @Bean 39 | fun backendWebClient(builder: WebClient.Builder): WebClient { 40 | LOG.info("Initializing backend API client with url: {}", apiUrl) 41 | return builder.baseUrl(apiUrl) 42 | .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) 43 | .build(); 44 | } 45 | } 46 | 47 | fun main(args: Array) { 48 | runApplication(*args) 49 | } 50 | -------------------------------------------------------------------------------- /djl-spring-boot-web/src/main/kotlin/com/aws/samples/djldemoweb/backend/ObjectDetectionClient.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://aws.amazon.com/apache2.0/ 8 | * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | package com.aws.samples.djldemoweb.backend 14 | 15 | import com.aws.samples.djl.spring.model.InferenceResponse 16 | import org.springframework.stereotype.Service 17 | import org.springframework.web.reactive.function.client.WebClient 18 | import reactor.core.publisher.Mono 19 | 20 | @Service 21 | class ObjectDetectionClient(private val webClient: WebClient) { 22 | 23 | fun detect(file: String, generateOutputImage: Boolean?): Mono { 24 | return this.webClient.get().uri{ 25 | builder -> builder.path("/inference") 26 | .queryParam("file", file) 27 | .queryParam("generateOutputImage", generateOutputImage) 28 | .build() 29 | } 30 | .retrieve() 31 | .bodyToMono(InferenceResponse::class.java) 32 | } 33 | } -------------------------------------------------------------------------------- /djl-spring-boot-web/src/main/kotlin/com/aws/samples/djldemoweb/controller/IndexController.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://aws.amazon.com/apache2.0/ 8 | * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | package com.aws.samples.djldemoweb.controller 14 | 15 | import org.springframework.stereotype.Controller 16 | import org.springframework.web.bind.annotation.RequestMapping 17 | 18 | /** 19 | * Index controller. 20 | */ 21 | @Controller 22 | class IndexController { 23 | 24 | @RequestMapping("/") 25 | fun index(): String { 26 | return "index" 27 | } 28 | } -------------------------------------------------------------------------------- /djl-spring-boot-web/src/main/kotlin/com/aws/samples/djldemoweb/controller/ObjectDetectionController.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://aws.amazon.com/apache2.0/ 8 | * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | package com.aws.samples.djldemoweb.controller 14 | 15 | import com.aws.samples.djl.spring.common.S3ImageDownloader 16 | import com.aws.samples.djl.spring.common.S3ImageUploader 17 | import com.aws.samples.djldemoweb.backend.ObjectDetectionClient 18 | import com.aws.samples.djldemoweb.form.ObjectDetectionForm 19 | import com.fasterxml.jackson.databind.ObjectMapper 20 | import org.slf4j.Logger 21 | import org.slf4j.LoggerFactory 22 | import org.springframework.core.io.InputStreamResource 23 | import org.springframework.core.io.Resource 24 | import org.springframework.stereotype.Controller 25 | import org.springframework.ui.Model 26 | import org.springframework.web.bind.annotation.* 27 | import java.time.Duration 28 | 29 | 30 | @Controller 31 | class ObjectDetectionController(private val uploader: S3ImageUploader, 32 | private val downloader: S3ImageDownloader, 33 | private val apiClient: ObjectDetectionClient) { 34 | 35 | companion object { 36 | val LOG: Logger = LoggerFactory.getLogger(ObjectDetectionController::class.java) 37 | } 38 | 39 | @RequestMapping("/object-detection/inbox") 40 | fun listFiles(model: Model): String { 41 | model.addAttribute("files", downloader.listFolder("inbox")) 42 | return "object-detection-files" 43 | } 44 | 45 | @RequestMapping("/object-detection/inbox/new-object-detection") 46 | fun newObjectDetection(model: Model): String { 47 | model.addAttribute("objectDetectionForm", ObjectDetectionForm()) 48 | return "new-object-detection" 49 | } 50 | 51 | @RequestMapping("/object-detection/inbox", method = [RequestMethod.POST]) 52 | fun detectObjects(@ModelAttribute form: ObjectDetectionForm, model: Model): String { 53 | if(form.file?.originalFilename == null) { 54 | return "object-detection-inbox" 55 | } 56 | val fileName = form.file?.originalFilename ?: "" 57 | uploader.upload(form.file?.inputStream, form.file?.size ?:0, fileName) 58 | val results = apiClient.detect(fileName, true).block(Duration.ofSeconds(30)) 59 | val jsonResults = ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(results) 60 | LOG.info("Object detection results: {} ", jsonResults) 61 | model.addAttribute("files", downloader.listFolder("inbox")) 62 | model.addAttribute("results", jsonResults) 63 | model.addAttribute("originalFile", form.file?.originalFilename) 64 | model.addAttribute("resultFile", form.file?.originalFilename.plus(".png")) 65 | return "object-detection-files" 66 | } 67 | 68 | @RequestMapping("/object-detection/images/inbox/{file-name}") 69 | @ResponseBody 70 | fun getInboxImage(@PathVariable("file-name") fileName: String) : Resource { 71 | return InputStreamResource(downloader.downloadStream("inbox/".plus(fileName))) 72 | } 73 | 74 | @RequestMapping("/object-detection/images/outbox/{file-name}") 75 | @ResponseBody 76 | fun getOutboxImage(@PathVariable("file-name") fileName: String) : Resource { 77 | return InputStreamResource(downloader.downloadStream("outbox/".plus(fileName))) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /djl-spring-boot-web/src/main/kotlin/com/aws/samples/djldemoweb/form/ObjectDetectionForm.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://aws.amazon.com/apache2.0/ 8 | * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | package com.aws.samples.djldemoweb.form 14 | 15 | import org.jetbrains.annotations.NotNull 16 | import org.springframework.web.multipart.MultipartFile 17 | 18 | class ObjectDetectionForm { 19 | @NotNull 20 | var file: MultipartFile? = null 21 | } -------------------------------------------------------------------------------- /djl-spring-boot-web/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.servlet.multipart.max-file-size=4MB 2 | spring.servlet.multipart.max-request-size=4MB 3 | server.port=8081 4 | aws.s3.bucket=djl-demo 5 | aws.s3.download-folder=outbox 6 | aws.s3.upload-folder=inbox 7 | djl.app.url=${DJL_APP_URL:http://localhost:8080} -------------------------------------------------------------------------------- /djl-spring-boot-web/src/main/resources/static/style.css: -------------------------------------------------------------------------------- 1 | body.compensate-for-scrollbar { 2 | overflow: hidden 3 | } 4 | body 5 | { 6 | font-family: Metropolis, sans-serif; 7 | } 8 | 9 | input { 10 | margin: 1; 11 | padding: 1; 12 | } 13 | 14 | #file-list { 15 | border-collapse: collapse; 16 | width: 100%; 17 | } 18 | #file-list td, #file-list th { 19 | border: 1px solid #ddd; 20 | padding: 8px; 21 | } 22 | #file-list tr:nth-child(even){background-color: #f2f2f2;} 23 | 24 | #file-list tr:hover { 25 | background-color: #ddd; 26 | text-decoration: underline; 27 | color: blue 28 | } 29 | 30 | #file-list th { 31 | padding-top: 12px; 32 | padding-bottom: 12px; 33 | text-align: left; 34 | background-color: rgba(76, 175, 79, 0.377); 35 | text-decoration: none; 36 | color: white; 37 | } 38 | img { 39 | max-width: 100% 40 | } 41 | 42 | p { 43 | line-height: 1.7em 44 | } 45 | 46 | a { 47 | color: inherit; 48 | text-decoration: none; 49 | position: relative; 50 | transition: all .2s 51 | } 52 | 53 | a:hover { 54 | cursor: pointer 55 | } 56 | 57 | footer ul, 58 | header ul { 59 | padding: 0; 60 | list-style: none 61 | } 62 | 63 | a.link-animate { 64 | display: inline-block 65 | } 66 | 67 | a.link-darken { 68 | transition: color .2s 69 | } 70 | 71 | a.link-darken:focus, 72 | a.link-darken:hover { 73 | color: #333 74 | } 75 | 76 | a.link-lighten:focus, 77 | a.link-lighten:hover { 78 | color: #fff 79 | } 80 | 81 | a.link-animate:before { 82 | content: ""; 83 | position: absolute; 84 | width: 100%; 85 | height: 2px; 86 | bottom: -3px; 87 | left: 0; 88 | background-color: #086dc3; 89 | visibility: hidden; 90 | transform-origin: top left; 91 | -webkit-transform: scaleX(0); 92 | transform: scaleX(0); 93 | -webkit-transition: all .3s ease-in-out 0s; 94 | transition: all .3s ease-in-out 0s 95 | } 96 | 97 | a.link-animate:focus:before, 98 | a.link-animate:hover:before { 99 | visibility: visible; 100 | -webkit-transform: scaleX(1); 101 | transform: scaleX(1) 102 | } 103 | 104 | a.link-animate.link-darken:before { 105 | background-color: #333 106 | } 107 | 108 | .blue { 109 | color: #086dc3 110 | } 111 | 112 | .green { 113 | color: #6db33f 114 | } 115 | 116 | .white { 117 | color: #fff 118 | } 119 | 120 | .bg-white { 121 | background-color: #fff 122 | } 123 | 124 | .bg-lightblue { 125 | background: #ebf2f2 126 | } 127 | 128 | .bg-black { 129 | background-color: #191e1e 130 | } 131 | 132 | .cursor-d:hover { 133 | cursor: default 134 | } 135 | 136 | .pointer:hover { 137 | cursor: pointer 138 | } 139 | 140 | .of-hide { 141 | overflow: hidden 142 | } 143 | 144 | .bold { 145 | font-weight: 700 146 | } 147 | 148 | .h1, 149 | .h2, 150 | .h3, 151 | .h4, 152 | .h5, 153 | h1, 154 | h2, 155 | h3, 156 | h4, 157 | h5 { 158 | font-family: Metropolis, sans-serif; 159 | font-weight: 500 160 | } 161 | 162 | .h1 { 163 | font-size: 3.75rem; 164 | line-height: 1em; 165 | margin-top: .4em; 166 | margin-bottom: .4em 167 | } 168 | 169 | .h2 { 170 | font-size: 2.375rem 171 | } 172 | 173 | .h3 { 174 | font-size: 1.5rem; 175 | margin-top: .4em; 176 | margin-bottom: .4em 177 | } 178 | 179 | .bigger { 180 | font-size: 1.625rem; 181 | font-weight: 500 182 | } 183 | 184 | .big, 185 | .bigger { 186 | font-family: Metropolis, sans-serif 187 | } 188 | 189 | .big { 190 | font-size: 1.5rem; 191 | font-weight: 400 192 | } 193 | 194 | .small { 195 | font-size: .9375rem 196 | } 197 | 198 | .smaller { 199 | font-size: .875rem 200 | } 201 | 202 | .smallest { 203 | font-size: .8125rem 204 | } 205 | 206 | .thin { 207 | font-weight: 500 208 | } 209 | 210 | .antialiased { 211 | -webkit-font-smoothing: antialiased; 212 | -moz-osx-font-smoothing: grayscale 213 | } 214 | 215 | .ws-nowrap { 216 | white-space: nowrap 217 | } 218 | 219 | .tt-none { 220 | text-transform: none 221 | } 222 | 223 | .center { 224 | text-align: center 225 | } 226 | 227 | .uppercase { 228 | text-transform: uppercase 229 | } 230 | 231 | .button.animate.white:before { 232 | background-color: #fff !important 233 | } 234 | 235 | .button.animate:focus, 236 | .button.animate:hover, 237 | a.blog-preview--readmore:hover { 238 | color: #fff 239 | } 240 | 241 | .button.animate:focus:before, 242 | .button.animate:hover:before, 243 | a.blog-preview--readmore:hover:before { 244 | -webkit-transform: translateX(0); 245 | transform: translateX(0) 246 | } 247 | 248 | .button.transparent { 249 | background: 0 0 250 | } 251 | 252 | .button.white { 253 | border: 2px solid #fff; 254 | color: #fff; 255 | transition: border .2s, color .2s 256 | } 257 | 258 | .button.white.animate:focus, 259 | .button.white.animate:hover { 260 | color: #191e1e 261 | } 262 | 263 | .button.button-small { 264 | padding: 4px 12px; 265 | font-size: 12px; 266 | position: relative 267 | } 268 | 269 | .button.button-small.button-icon { 270 | padding-left: 25px; 271 | position: relative 272 | } 273 | 274 | .button.button-small.button-icon .fab { 275 | position: absolute; 276 | top: 7px; 277 | left: 9px 278 | } 279 | 280 | .flex { 281 | display: flex 282 | } 283 | 284 | .fd-col { 285 | flex-direction: column 286 | } 287 | 288 | .flex-wrap { 289 | flex-wrap: wrap 290 | } 291 | 292 | .grow-0 { 293 | flex-grow: 0 294 | } 295 | 296 | .jc-between { 297 | justify-content: space-between 298 | } 299 | 300 | .jc-center { 301 | justify-content: center 302 | } 303 | 304 | .ai-center { 305 | align-items: center 306 | } 307 | 308 | .as-fs { 309 | align-self: flex-start 310 | } 311 | 312 | .mx-auto { 313 | margin-left: auto; 314 | margin-right: auto 315 | } 316 | 317 | .mw-100 { 318 | max-width: 100px 319 | } 320 | 321 | .quarter { 322 | width: 23.5% 323 | } 324 | 325 | .container, 326 | .container-fluid, 327 | .main-body--wrapper { 328 | max-width: 1140px; 329 | padding: 0 20px; 330 | margin: 0 auto 331 | } 332 | 333 | .inline-block { 334 | display: inline-block 335 | } 336 | 337 | .block { 338 | display: block 339 | } 340 | 341 | .border { 342 | border: 2px solid #ebf2f2 343 | } 344 | 345 | .border-dark { 346 | border: 1px solid #d9e9e9 347 | } 348 | 349 | .relative { 350 | height: 100%; 351 | width: 100% 352 | } 353 | 354 | .rel, 355 | .relative { 356 | position: relative 357 | } 358 | 359 | .abs { 360 | position: absolute 361 | } 362 | 363 | .z-1 { 364 | z-index: 1 365 | } 366 | 367 | .z-2 { 368 | z-index: 2 369 | } 370 | 371 | .z-3 { 372 | z-index: 3 373 | } 374 | 375 | .z-4 { 376 | z-index: 4 377 | } 378 | 379 | .z-5 { 380 | z-index: 5 381 | } 382 | 383 | .mt-0 { 384 | margin-top: 0 385 | } 386 | 387 | .mt-20 { 388 | margin-top: 20px 389 | } 390 | 391 | .mt-50 { 392 | margin-top: 50px 393 | } 394 | 395 | .mr-40 { 396 | margin-right: 40px 397 | } 398 | 399 | .ht-6 { 400 | height: 60px 401 | } 402 | 403 | .ht-7 { 404 | height: 70px 405 | } 406 | 407 | .max-ht-8 { 408 | max-height: 80px 409 | } 410 | 411 | .mb-0 { 412 | margin-bottom: 0 413 | } 414 | 415 | .mb-05 { 416 | margin-bottom: 5px 417 | } 418 | 419 | .mb-1 { 420 | margin-bottom: 10px 421 | } 422 | 423 | .mb-2 { 424 | margin-bottom: 20px 425 | } 426 | 427 | .mb-3 { 428 | margin-bottom: 40px 429 | } 430 | 431 | .mb-4 { 432 | margin-bottom: 60px 433 | } 434 | 435 | .mb-5 { 436 | margin-bottom: 80px 437 | } 438 | 439 | .mb-6 { 440 | margin-bottom: 100px 441 | } 442 | 443 | .mb-7 { 444 | margin-bottom: 120px 445 | } 446 | 447 | .mb-8 { 448 | margin-bottom: 140px 449 | } 450 | 451 | .px-0 { 452 | padding-left: 0; 453 | padding-right: 0 454 | } 455 | 456 | .px-60 { 457 | padding-left: 60px; 458 | padding-right: 60px 459 | } 460 | 461 | .py-40 { 462 | padding-top: 40px; 463 | padding-bottom: 40px 464 | } 465 | 466 | .py-50 { 467 | padding-top: 50px; 468 | padding-bottom: 50px 469 | } 470 | 471 | .py-70 { 472 | padding-top: 70px; 473 | padding-bottom: 70px 474 | } 475 | 476 | .py-80 { 477 | padding-top: 80px; 478 | padding-bottom: 80px 479 | } 480 | 481 | .py-100 { 482 | padding-top: 100px; 483 | padding-bottom: 100px 484 | } 485 | 486 | .rad-5 { 487 | border-radius: 5px 488 | } 489 | 490 | .rad-50 { 491 | border-radius: 50% 492 | } 493 | 494 | .quoted-with-video .rad-50, 495 | .quoted .rad-50 { 496 | height: 45px; 497 | width: 45px; 498 | margin-right: 15px 499 | } 500 | 501 | .border-box { 502 | box-sizing: border-box 503 | } 504 | 505 | .shadow { 506 | box-shadow: 0 5px 30px 0 rgba(108, 135, 135, .5) 507 | } 508 | 509 | .shadow-hover { 510 | transition: all .2s, transform .2s; 511 | transform: translateY(0); 512 | position: relative; 513 | box-shadow: none; 514 | top: 0 515 | } 516 | 517 | .shadow-hover:focus, 518 | .shadow-hover:hover { 519 | transform: translateY(-3px); 520 | top: -3px; 521 | box-shadow: 0 10px 20px 0 rgba(108, 135, 135, .2) 522 | } 523 | 524 | .indent { 525 | padding-left: 20px 526 | } 527 | 528 | .big-quote { 529 | height: 50px; 530 | margin: 0 auto; 531 | display: block 532 | } 533 | 534 | code { 535 | border: solid 1px #5882FA; 536 | font-family: monospace; 537 | font-size: 90%; 538 | } 539 | 540 | textarea { 541 | font-family: "Roboto Mono", Menlo, monospace; 542 | font-size: 0.8rem; 543 | line-height: 1.2; 544 | } 545 | 546 | blockquote { 547 | line-height: 1.5em 548 | } 549 | 550 | .quoted { 551 | text-transform: uppercase; 552 | font-size: .875rem; 553 | font-weight: 700; 554 | -webkit-font-smoothing: antialiased; 555 | -moz-osx-font-smoothing: grayscale 556 | } 557 | 558 | .caption { 559 | font-size: 13px; 560 | font-style: italic; 561 | text-align: center; 562 | opacity: .7 563 | } 564 | 565 | .new-tag { 566 | font-size: .875rem; 567 | border-radius: 20px; 568 | color: #fff; 569 | font-weight: 700; 570 | background: #06c; 571 | padding: 2px 15px; 572 | -webkit-font-smoothing: antialiased; 573 | -moz-osx-font-smoothing: grayscale 574 | } 575 | 576 | .vdivider { 577 | width: 2px; 578 | background: #b6d8d1 579 | } 580 | 581 | hr.light { 582 | background: #ebf2f2 583 | } 584 | 585 | hr.dark, 586 | hr.light { 587 | border: 0; 588 | height: 2px 589 | } 590 | 591 | .terminal, 592 | hr.dark { 593 | background: #191e1e 594 | } 595 | 596 | .terminal { 597 | color: #fff; 598 | padding: 70px 55px; 599 | font-size: 15px; 600 | position: relative; 601 | box-shadow: 0 8px 15px 0 rgba(0, 0, 0, .2) 602 | } 603 | 604 | .terminal .top { 605 | display: flex; 606 | justify-content: space-between; 607 | width: 45px; 608 | position: absolute; 609 | left: 20px; 610 | top: 20px 611 | } 612 | 613 | .terminal .circle { 614 | background-color: #ccc 615 | } 616 | 617 | .terminal .circle, 618 | .terminal .x-circle { 619 | width: 8px; 620 | height: 8px; 621 | border-radius: 50% 622 | } 623 | 624 | .terminal .x-circle { 625 | background-color: #f33 626 | } 627 | 628 | .terminal-blue { 629 | color: #85d9eb 630 | } 631 | 632 | .terminal-green { 633 | color: #56e27a 634 | } 635 | 636 | .terminal-lime { 637 | color: #d4f088 638 | } 639 | 640 | .topics-banner { 641 | background: #d7e7e7; 642 | background: linear-gradient(90deg, #d7e7e7 50%, #80ea6e 0); 643 | height: 40px; 644 | overflow: hidden 645 | } 646 | 647 | .topics-banner img { 648 | max-width: 1440px 649 | } 650 | 651 | .topics-banner #midshape { 652 | display: block; 653 | margin: 0 auto; 654 | height: 40px; 655 | max-width: 1440px 656 | } 657 | 658 | .topic-info { 659 | width: 65% 660 | } 661 | 662 | .topic-hero .big { 663 | margin-top: 0 664 | } 665 | 666 | .topic-icon { 667 | width: 24%; 668 | transform: translateY(20px) 669 | } 670 | 671 | .topic-getstarted .flex { 672 | max-width: 565px; 673 | margin: 0 auto 40px 674 | } 675 | 676 | .topics-resources .thumbnail:hover+.link-darken { 677 | color: #191e1e 678 | } 679 | 680 | #quote .thumbnail { 681 | width: 220px; 682 | margin-right: 40px 683 | } 684 | 685 | .thumbnail img { 686 | max-width: 100%; 687 | display: block 688 | } 689 | 690 | .cornell>.flex { 691 | border-top: 2px solid #191e1e 692 | } 693 | 694 | .cornell .right { 695 | width: 65.5% 696 | } 697 | 698 | .cornell .left h2:first-child, 699 | .cornell .right p:first-child { 700 | margin-top: 0 701 | } 702 | 703 | .cornell .diagram-container { 704 | background: #ebf2f2; 705 | padding: 50px 706 | } 707 | 708 | @media(max-width:800px) { 709 | .cornell .diagram-container { 710 | padding: 20px 711 | } 712 | } 713 | 714 | .cornell .diagram-container img { 715 | max-width: 80% 716 | } 717 | 718 | .half-cornell .half { 719 | border-top: 2px solid #191e1e; 720 | width: 47.5% 721 | } 722 | 723 | .half { 724 | width: 48.5% 725 | } 726 | 727 | .topics-half { 728 | width: 46.5% 729 | } 730 | 731 | .third { 732 | width: 31.5% 733 | } 734 | 735 | .two-thirds { 736 | width: 63.5% 737 | } 738 | 739 | .w-220 { 740 | width: 220px 741 | } 742 | 743 | .mw-240 { 744 | max-width: 240px 745 | } 746 | 747 | .mw-340 { 748 | max-width: 340px 749 | } 750 | 751 | .mw-580 { 752 | max-width: 580px 753 | } 754 | 755 | .mw-740 { 756 | max-width: 740px 757 | } 758 | 759 | .mw-850 { 760 | max-width: 850px 761 | } 762 | 763 | .mw-945 { 764 | max-width: 945px 765 | } 766 | 767 | .mw-1440 { 768 | max-width: 1440px 769 | } 770 | 771 | .m-0 { 772 | margin: 0 773 | } 774 | 775 | .play { 776 | background-color: #6db33f; 777 | height: 50px; 778 | width: 50px; 779 | position: absolute; 780 | bottom: 0; 781 | left: 0; 782 | transition: background-color .2s 783 | } 784 | 785 | .thumbnail:focus .play, 786 | .thumbnail:hover .play { 787 | background-color: #7fea6f 788 | } 789 | 790 | .play .triangle { 791 | margin: auto; 792 | top: 0; 793 | bottom: 0; 794 | left: 0; 795 | right: 0; 796 | width: 0; 797 | height: 0; 798 | position: absolute; 799 | border-top: 10px solid transparent; 800 | border-bottom: 10px solid transparent; 801 | border-left: 15px solid #fff 802 | } 803 | 804 | .backlink .arrow { 805 | transform: rotate(45deg) translateY(-6px); 806 | -webkit-transform: rotate(135deg); 807 | border: solid #06c; 808 | border-width: 0 2px 2px 0; 809 | display: inline-block; 810 | padding: 3px; 811 | margin-right: 4px; 812 | transition: border .2s 813 | } 814 | 815 | .backlink:focus .arrow, 816 | .backlink:hover .arrow { 817 | border: solid #191e1e; 818 | border-width: 0 2px 2px 0 819 | } 820 | 821 | .noscroll { 822 | overflow: hidden 823 | } 824 | 825 | -------------------------------------------------------------------------------- /djl-spring-boot-web/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DJL Demo 5 | 6 | 7 | 8 | 9 |

DJL (Deep Learning with Java) and Spring Boot

10 |

Examples: Inference

11 | 12 | Object Detection 13 | 14 | 15 | -------------------------------------------------------------------------------- /djl-spring-boot-web/src/main/resources/templates/object-detection-files.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DJL Demo 5 | 6 | 7 | 8 | 18 | 19 | 20 | 41 |
42 |

Select File for Detection

43 |
45 | 46 | 47 |
48 |
49 |
50 |