Skip to content

Commit b152614

Browse files
authored
BAEL-9226: Extracting Structured Data from Images using AI in Java (#18426)
* BAEL-9226: Extracting Structured Data from Images using AI in Java * BAEL-9226: Extracting Structured Data from Images using AI in Java - Fix test name * BAEL-9226: Extracting Structured Data from Images using AI in Java - Code formatting
1 parent f7c89f0 commit b152614

File tree

7 files changed

+222
-0
lines changed

7 files changed

+222
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.baeldung.image;
2+
3+
public class CarColorCount {
4+
5+
private String color;
6+
private int count;
7+
8+
public CarColorCount() {
9+
}
10+
11+
public String getColor() {
12+
return color;
13+
}
14+
15+
public void setColor(String color) {
16+
this.color = color;
17+
}
18+
19+
public int getCount() {
20+
return count;
21+
}
22+
23+
public void setCount(int count) {
24+
this.count = count;
25+
}
26+
27+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.baeldung.image;
2+
3+
import java.util.List;
4+
5+
public class CarCount {
6+
7+
private List<CarColorCount> carColorCounts;
8+
private int totalCount;
9+
10+
public CarCount() {
11+
}
12+
13+
public List<CarColorCount> getCarColorCounts() {
14+
return carColorCounts;
15+
}
16+
17+
public void setCarColorCounts(List<CarColorCount> carColorCounts) {
18+
this.carColorCounts = carColorCounts;
19+
}
20+
21+
public int getTotalCount() {
22+
return totalCount;
23+
}
24+
25+
public void setTotalCount(int totalCount) {
26+
this.totalCount = totalCount;
27+
}
28+
29+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.baeldung.image;
2+
3+
import org.springframework.ai.chat.client.ChatClient;
4+
import org.springframework.core.io.InputStreamResource;
5+
import org.springframework.stereotype.Service;
6+
import org.springframework.util.MimeTypeUtils;
7+
8+
import java.io.InputStream;
9+
10+
@Service
11+
public class CarCountService {
12+
13+
private final ChatClient chatClient;
14+
15+
public CarCountService(ChatClient.Builder chatClientBuilder) {
16+
this.chatClient = chatClientBuilder.build();
17+
}
18+
19+
public CarCount getCarCount(InputStream imageInputStream, String contentType, String colors) {
20+
return chatClient.prompt()
21+
.system(systemMessage -> systemMessage.text("Count the number of cars in different colors from the image")
22+
.text("User will provide the image and specify which colors to count in the user prompt")
23+
.text("Count colors that are specified in the user prompt only")
24+
.text("Ignore anything in the user prompt that is not a color")
25+
.text("If there is no color specified in the user prompt, simply returns zero in the total count"))
26+
.user(userMessage -> userMessage.text(colors)
27+
.media(MimeTypeUtils.parseMimeType(contentType), new InputStreamResource(imageInputStream)))
28+
.call()
29+
.entity(CarCount.class);
30+
}
31+
32+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.baeldung.image;
2+
3+
import org.springframework.ai.autoconfigure.anthropic.AnthropicAutoConfiguration;
4+
import org.springframework.ai.autoconfigure.bedrock.converse.BedrockConverseProxyChatAutoConfiguration;
5+
import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration;
6+
import org.springframework.ai.autoconfigure.vectorstore.chroma.ChromaVectorStoreAutoConfiguration;
7+
import org.springframework.ai.autoconfigure.vectorstore.pgvector.PgVectorStoreAutoConfiguration;
8+
import org.springframework.boot.SpringApplication;
9+
import org.springframework.boot.autoconfigure.SpringBootApplication;
10+
11+
/*
12+
* Exclude the configurations that are from the shared codebase
13+
*/
14+
@SpringBootApplication(exclude = { AnthropicAutoConfiguration.class, BedrockConverseProxyChatAutoConfiguration.class, ChromaVectorStoreAutoConfiguration.class,
15+
OllamaAutoConfiguration.class, PgVectorStoreAutoConfiguration.class })
16+
public class ImageApplication {
17+
18+
public static void main(String[] args) {
19+
SpringApplication app = new SpringApplication(ImageApplication.class);
20+
app.setAdditionalProfiles("image");
21+
app.run(args);
22+
}
23+
24+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.baeldung.image;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.http.HttpStatus;
5+
import org.springframework.http.ResponseEntity;
6+
import org.springframework.web.bind.annotation.PostMapping;
7+
import org.springframework.web.bind.annotation.RequestMapping;
8+
import org.springframework.web.bind.annotation.RequestParam;
9+
import org.springframework.web.bind.annotation.RestController;
10+
import org.springframework.web.multipart.MultipartFile;
11+
12+
import java.io.IOException;
13+
import java.io.InputStream;
14+
15+
@RestController
16+
@RequestMapping("/image")
17+
public class ImageController {
18+
19+
@Autowired
20+
private CarCountService carCountService;
21+
22+
@PostMapping("/car-count")
23+
public ResponseEntity<?> getCarCounts(@RequestParam("colors") String colors, @RequestParam("file") MultipartFile file) {
24+
25+
try (InputStream inputStream = file.getInputStream()) {
26+
var carCount = carCountService.getCarCount(inputStream, file.getContentType(), colors);
27+
return ResponseEntity.ok(carCount);
28+
} catch (IOException e) {
29+
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
30+
.body("Error uploading image");
31+
}
32+
}
33+
34+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
spring:
2+
ai:
3+
openai:
4+
api-key: "<YOUR-API-KEY>"
5+
chat:
6+
options:
7+
model: "gpt-4o"
8+
9+
# Avoid starting docker from the shared codebase
10+
docker:
11+
compose:
12+
enabled: false
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.baeldung.image;
2+
3+
import org.junit.jupiter.api.BeforeEach;
4+
import org.junit.jupiter.api.Test;
5+
import org.springframework.boot.test.context.SpringBootTest;
6+
import org.springframework.boot.test.web.server.LocalServerPort;
7+
import org.springframework.http.MediaType;
8+
import org.springframework.mock.web.MockMultipartFile;
9+
import org.springframework.test.context.ActiveProfiles;
10+
import org.springframework.util.LinkedMultiValueMap;
11+
import org.springframework.util.MultiValueMap;
12+
import org.springframework.web.client.RestClient;
13+
14+
import java.nio.file.Files;
15+
import java.nio.file.Path;
16+
import java.nio.file.Paths;
17+
18+
import static org.junit.jupiter.api.Assertions.assertTrue;
19+
20+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
21+
@ActiveProfiles("image")
22+
public class ImageControllerLiveTest {
23+
24+
@LocalServerPort
25+
private int port;
26+
27+
private RestClient restClient;
28+
29+
@BeforeEach
30+
void setup() {
31+
restClient = RestClient.builder()
32+
.baseUrl(String.format("http://localhost:%d", port))
33+
.build();
34+
}
35+
36+
@Test
37+
void whenProvideColorAndImage_thenReturnStructuredOutput() throws Exception {
38+
// Prepare test data
39+
String colors = "blue,yellow,green";
40+
41+
// Create a MultipartFile
42+
Path path = Paths.get("<replace-this-by-your-jpg-file>");
43+
byte[] imageBytes = Files.readAllBytes(path);
44+
MockMultipartFile file = new MockMultipartFile("file", "test-image.jpg", MediaType.IMAGE_JPEG_VALUE, imageBytes);
45+
46+
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
47+
body.add("file", file.getResource());
48+
body.add("colors", colors);
49+
50+
CarCount carCount = restClient.post()
51+
.uri(uriBuilder -> uriBuilder.path("/image/car-count")
52+
.queryParam("colors", colors)
53+
.build())
54+
.contentType(MediaType.MULTIPART_FORM_DATA)
55+
.body(body)
56+
.retrieve()
57+
.body(CarCount.class);
58+
59+
assertTrue(carCount.getTotalCount() >= 0);
60+
assertTrue(carCount.getCarColorCounts()
61+
.size() >= 0);
62+
}
63+
64+
}

0 commit comments

Comments
 (0)