[Sunny Braille] 팀 합류와 동시에 타이거 팀이 되다..! 장고 코드를 스프링 코드로 바꿔주세요!: Mathpix API 사용해서 수학문제 사진에서 OCR 처리하기 (수식 인식하기)

2024. 2. 14. 16:30· PROJECT/Sunny Braille
목차
  1. 💋 구현 내용
  2. ✔️ 목표
  3. ✔️ 구현해야 하는 내용
  4. ✔️ [개인적 궁금증] Mathpix API 반환을 Latex 파일로 받는 이유
  5. ✔️ 느낀점
  6. 💋 Mathpix API에 PDF 파일 보내고 식별자 반환 받기!
  7. ✔️ Mathpix API에 곧바로 파일 보내는 방식 선택!
  8. ✔️ 포스트맨으로 pdf 전송하는 방법..?
  9. ✔️ Mathpix API 문서대로 ‘제대로’ 호출하기
  10. 💋 참고자료
반응형
반응형

 

 

 

시각장애인을 위한 빠르고 정확한 수학교재 인공지능 점자 변환 서비스, Sunny Braille

어떤 교재든 텍스트 뿐만 아니라 수식도 점자로 변환해주는 교육용 AI 점역 소프트웨어

 

 

 

해바라기 팀 소개 영상 ⇒ https://www.youtube.com/watch?v=cuYIEpQx2po&t=8s

 

Sunny Braille 소스 코드 ⇒ https://github.com/sunnybraille

 

시각장애인을 위한 빠르고 정확한 수학교재 인공지능 점자 변환 서비스, Sunny Braille

어떤 교재든 텍스트 뿐만 아니라 수식도 점자로 변환해주는 교육용 AI 점역 소프트웨어. 시각장애인을 위한 빠르고 정확한 수학교재 인공지능 점자 변환 서비스, Sunny Braille has 4 repositories available

github.com

 

 

포스팅 관련 PR ⇒ https://github.com/Sunflower-yonsei/sunflower-server/pull/13

 

feat: pdf upload api 구현 by gitchannn · Pull Request #13 · Sunflower-yonsei/sunflower-server

주요 변경사항 현재 작업중.. 관련 이슈 closes #8

github.com

 

💋 구현 내용

✔️ 목표

현재 해바라기 팀의 서버는 젠부 장고로 구현되어 있다.

초창기에 자바&스프링을 다룰 수 있는 멤버가 없었기 때문 + 다양한 그나름의 이유로 장고로 구현되어 있었는데,

새로운 팀원을 구할 때에도 언어가 장벽이 되고 있고 장고를 사용하던 팀원의 이탈로 이제 유지보수가 정말 어려워졌다

게다가 장고에는 우리 팀이 직접 제작한 비밀 점역 알고리즘과 백엔드 코드가 섞여 있어서 진짜 헬이 되어버렸다.

 

합류 후 나의 첫 미션은 해바라기의 핵심 기능(이자 사실 이제까지는 기능의 전부인 2가지) API를 빠르게 스프링으로 재구현하고, 비밀 점역 알고리즘은 별도의 API로 분리해 낸다.

++ 또한 추후에 추가될 요구사항에도 유연하게 대응할 수 있게 해야 한다.

 

 

 

Sunny Braille 서비스에서는 사용자가 수학 문제를 촬영한 pdf를 업로드하면, 클라이언트는 pdf를 form 형태로 서버에 보낸다.

이제 나(서버개발자..)는 그 pdf를 포함해서 Mathpix API를 호출해서 수식 사진 ⇒ Latex 파일 으로 변경하는 작업을 해야 한다.

 

개발중인 API에 대한 이슈는 이 링크를 들어가면 있당

 

✔️ 구현해야 하는 내용

Mathpix API를 2번 호출해야 한다.

 

  1. pdf 파일로 사진을 보내서 pdf로 변환하면, api에서는 pdf_id를 반환해준다.
  2. 이 pdf_id를 다시 가지고 호출해서 변환된 결과의 Latex 파일을 받을 수 있다.

 

그러고, 이제 받은 Latex 파일을 내 데이터베이스에 저장하고 (후에 캐시와 비슷하게 사용할까 생각중이기 때문에 일단 저장하기로 했다.)

저장된 결과를 translations라는 데이터베이스에 저장한 후, id 값을 클라이언트에 반환해줄 것이다.

 

이와 별도로 또 클라이언트에서 translations id를 보내오면 그때 Latex 파일을 반환하는 API를 하나 더 만들어야 할 것이다.

 

✔️ [개인적 궁금증] Mathpix API 반환을 Latex 파일로 받는 이유

Mathpix API 공식 문서에 따르면, 다양한 형식으로 결과물을 받을 수 있는데

 

 

우리는 그중 Latex 파일로 받기로 했다.

 

내가 합류하기 전부터 이미 이렇게 하고 있어서 이유를 물어봤는데

 

  1. Tex(LaTeX) 형식이 수식을 표현하는데 편함
  2. 문법이 정형화 되어 있음
  3. 대부분 OCR에서 수식을 표현할때 레이텍을 사용함

 

이 세가지 이유라고 한다.

 

✔️ 느낀점

 

구현 방법에 앞서 가장 중요한 느낀점부터,,,!

 

일단 REST API를 통해서 pdf를 다룬 적도 없는데, Latex 형식 또한 너무 낯설어서 생각보다 많은 시간이 걸렸다.

막 그냥 일단 대강 해보려고 했는데 역시나 공식문서 찬찬히 읽는게 가장 돌아가는 듯 해도 가장 빠른 길,, 항상 잊는듯

 

 

💋 Mathpix API에 PDF 파일 보내고 식별자 반환 받기!

✔️ Mathpix API에 곧바로 파일 보내는 방식 선택!

이게 pdf 파일의 url을 보낼 수도 있고, pdf 파일을 곧바로 보낼 수도 있다.

기존에 팀에서는 장고 서버에 받은 pdf를 저장했다가 그 url을 호출하는데, 재구현하는 입장에서 딱히 사진을 가지고 있을 필요도 없고 해서 그냥 곧바로 pdf 파일을 보내는 편이 낫다고 생각했다.

 

친절하게 이런 예시로 보내라고도 공식문서에 써있길래 이대로 코드를 작성해봤는데,,,

 

curl --location --request POST 'https://api.mathpix.com/v3/pdf' \
--header 'app_id: APP_ID' \
--header 'app_key: APP_KEY' \
--form 'file=@"cs229-notes5.pdf"' \
--form 'options_json="{\"conversion_formats\": {\"docx\": true, \"tex.zip\": true}, \"math_inline_delimiters\": [\"$\", \"$\"], \"rm_spaces\": true}"'

 

 

도무지 테스트를 할 수가 없는 것…?!!

어떻게 해야하지 고민하다가…

 

✔️ 포스트맨으로 pdf 전송하는 방법..?

 

처음에는 http 파일 작성해서 어떻게 해보려고 했는데 계속 안되다가 갑자기 이전에 이리내가 작성했던 글 생각나서 해냈다. 진짜 휴

 

body를 form-data로 바꾸면, key에서 text나 file중에 선택할 수 있는데 파일로 변경하면 내 로컬에 있는 파일 중에 고를 수 있다.

 

 

 

이걸로 요청 보내면, 일단 내 톰캣 서버에서는 파일을 잘 받는 것 같다.

근데 이거 어떻게 테스트코드를 작성해야할지 진짜 모르겠다ㅋㅋㅋㅋㅋㅋㅌㅌ 일단 API 찬스 하나씩 써가면서 개발중,,

 

✔️ Mathpix API 문서대로 ‘제대로’ 호출하기

여러 에러를 만났지만, 역시 그냥 api 문서에 나와있는 모양 정말 그대로 제대로 호출하는 방법 말고는 해결할 방법이 없다.

 

공식문서에 나온 API 스펙을 열심히 읽어보다보면 이렇게 예시를 준다.

 

요청을 아래와 같이 보내면,

 

curl --location --request POST 'https://api.mathpix.com/v3/pdf' \
--header 'app_id: APP_ID' \
--header 'app_key: APP_KEY' \
--form 'file=@"cs229-notes5.pdf"' \
--form 'options_json="{\"conversion_formats\": {\"docx\": true, \"tex.zip\": true}, \"math_inline_delimiters\": [\"$\", \"$\"], \"rm_spaces\": true}"'

 

아래와 같은 모양의 응답을 받을 수 있다고 한다.

 

{
  "pdf_id": "5049b56d6cf916e713be03206f306f1a"
}

 

 

이렇게 응답 바디 파라미터들도 자세히 잘 알려주고 있음. 마음을 활짝 열고 보지 않으면 진짜 보기 싫게 생겼다..ㅋ

 

 

 

위에서 설명한 방법대로 포스트맨으로 보내봤더니 드디어 제대로 response body로 pdf id가 온다!

(쉬워보이지만 나름 여기까지 마음을 열고, 놀던 습관을 잡고 하느라 진짜 오래걸림. 이게 타이거팀 맞나..?)

 

 

 

나는 별도로 MathpixApiPdfProcessApiClient라는 컴포넌트를 별도로 만들어서 거기서 모든 작업을 처리하고 서비스 코드에는 응답으로 받은 pdf id만을 반환하는 메서드만을 열어두었다.

 

아래는 코드 전문!

 

package sunflower.server.client;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import static org.springframework.http.MediaType.MULTIPART_FORM_DATA;

@Slf4j
@Component
public class MathpixApiPdfProcessClient {

	private final String appURI;
	private final String appId;
	private final String appKey;
	private final RestTemplate restTemplate;

	public MathpixApiPdfProcessClient(
			@Value("${mathpix.app-uri}") String appURI,
			@Value("${mathpix.app-id}") String appId,
			@Value("${mathpix.app-key}") String appKey,
			RestTemplate restTemplate
	) {
		this.appURI = appURI;
		this.appId = appId;
		this.appKey = appKey;
		this.restTemplate = restTemplate;
	}

	public String requestPdfId(final MultipartFile file) {
		final ObjectMapper objectMapper = new ObjectMapper();

		final HttpHeaders requestHeader = createRequestHeader();
		final MultiValueMap<String, Object> requestBody = createRequestBody(file, objectMapper);
		final HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(requestBody, requestHeader);

		log.info("Request URI: {}", appURI);
		log.info("Request Headers: {}", requestHeader);
		log.info("Request Parameters: {}", requestBody);

		// send request to Mathpix API (process a pdf)
		final ResponseEntity<String> response = restTemplate.postForEntity(appURI, requestEntity, String.class);

		if (response.getStatusCode() != HttpStatus.OK) {
			log.warn("OCR 작업에 실패했습니다. Mathpix API 에러 메세지: {}", response.getBody());
			throw new RuntimeException("OCR 작업에 실패했습니다.");
		}
		try {
			final JsonNode root = objectMapper.readTree(response.getBody());
			String pdfID = root.get("pdf_id").asText();
			log.info("PDF ID: {}", pdfID);
			return pdfID;
		} catch (JsonProcessingException e) {
			throw new RuntimeException(e);
		}
	}

	private HttpHeaders createRequestHeader() {
		HttpHeaders requestHeader = new HttpHeaders();
		requestHeader.setContentType(MULTIPART_FORM_DATA);
		requestHeader.set("app_id", appId);
		requestHeader.set("app_key", appKey);
		return requestHeader;
	}

	private MultiValueMap<String, Object> createRequestBody(final MultipartFile file,
															final ObjectMapper objectMapper) {
		MultiValueMap<String, Object> requestBody = new LinkedMultiValueMap<>();
		requestBody.add("file", file.getResource());

		Map<String, Object> bodyMap = new HashMap<>();
		bodyMap.put("conversion_formats", Map.of("docx", true, "tex.zip", true));
		bodyMap.put("math_inline_delimiters", Arrays.asList("$", "$"));
		bodyMap.put("rm_spaces", true);
		String optionsJson = null;

		try {
			optionsJson = objectMapper.writeValueAsString(bodyMap);
		} catch (JsonProcessingException e) {
			throw new RuntimeException(e);
		}

		requestBody.add("options_json", optionsJson);
		return requestBody;
	}
}

 

 

그럼 다음에는 Latex 파일 받는 과정까지를 다뤄보도록 하겠습니당,,

 

 

💋 참고자료

  • https://docs.mathpix.com/#process-a-pdf
  • https://engineerinsight.tistory.com/295
  • https://finger-ineedyourhelp.tistory.com/80
  • https://docs.mathpix.com/#processing-status

 

 

도움이 되었다면, 공감/댓글을 달아주면 깃짱에게 큰 힘이 됩니다!🌟
비밀댓글과 메일을 통해 오는 개인적인 질문은 받지 않고 있습니다. 꼭 공개댓글로 남겨주세요!

 

반응형

'PROJECT > Sunny Braille' 카테고리의 다른 글

[Sunny Braille] 교내 세션 발표인줄 알았는데 반기문 옆자리인 건에 대하여 (GEEF 2024 초청받음)  (23) 2024.03.14
[Sunny Braille] 외부 API 호출, 비동기적으로 처리하기! (ft. Spring Event): 트랜잭션은 어떻게 해야할까,,?  (0) 2024.03.07
[Sunny Braille] Swagger로 더러워진 내 컨트롤러 코드 인공호흡: 인터페이스로 스웨거 코드 분리하는 방법  (0) 2024.03.04
[Sunny Braille] 시각장애인을 위한 수학교재 인공지능 점자변환 서비스, 해바라기 팀에 합류하다  (3) 2024.02.07
  1. 💋 구현 내용
  2. ✔️ 목표
  3. ✔️ 구현해야 하는 내용
  4. ✔️ [개인적 궁금증] Mathpix API 반환을 Latex 파일로 받는 이유
  5. ✔️ 느낀점
  6. 💋 Mathpix API에 PDF 파일 보내고 식별자 반환 받기!
  7. ✔️ Mathpix API에 곧바로 파일 보내는 방식 선택!
  8. ✔️ 포스트맨으로 pdf 전송하는 방법..?
  9. ✔️ Mathpix API 문서대로 ‘제대로’ 호출하기
  10. 💋 참고자료
'PROJECT/Sunny Braille' 카테고리의 다른 글
  • [Sunny Braille] 교내 세션 발표인줄 알았는데 반기문 옆자리인 건에 대하여 (GEEF 2024 초청받음)
  • [Sunny Braille] 외부 API 호출, 비동기적으로 처리하기! (ft. Spring Event): 트랜잭션은 어떻게 해야할까,,?
  • [Sunny Braille] Swagger로 더러워진 내 컨트롤러 코드 인공호흡: 인터페이스로 스웨거 코드 분리하는 방법
  • [Sunny Braille] 시각장애인을 위한 수학교재 인공지능 점자변환 서비스, 해바라기 팀에 합류하다
깃짱
깃짱
연새데학교 컴퓨터과학과 & 우아한테크코스 5기 백엔드 스타라이토 깃짱
반응형
깃짱
깃짱코딩
깃짱
전체
오늘
어제
  • 분류 전체보기
    • About. 깃짱
    • Weekly Momentum
      • 2024
    • PROJECT
      • AIGOYA LABS
      • Stamp Crush
      • Sunny Braille
    • 우아한테크코스5기
    • 회고+후기
    • Computer Science
      • Operating System
      • Computer Architecture
      • Network
      • Data Structure
      • Database
      • Algorithm
      • Automata
      • Data Privacy
      • Graphics
      • ETC
    • WEB
      • HTTP
      • Application
    • C, C++
    • JAVA
    • Spring
      • JPA
      • MVC
    • AI
    • MySQL
    • PostgreSQL
    • DevOps
      • AWS
      • 대규모 시스템 설계
    • frontend
      • HTML+CSS
    • NextJS
    • TEST
    • Industrial Engineering
    • Soft Skill
    • TIL
      • 2023
      • 2024
    • Linux
    • Git
    • IntelliJ
    • ETC
      • 日本語

블로그 메뉴

  • 홈
  • 깃허브

인기 글

최근 글

태그

  • 람다와스트림
  • 우테코5기
  • 레벨로그
  • Composition
  • 상속
  • 예외
  • TDD
  • 우아한테크코스5기
  • 스트림
  • 람다
  • 함수형프로그래밍
  • 우아한테크코스
  • 우테코
  • 조합
  • Java
  • 상속과조합
  • 컴포지션
  • lamda
  • OOP
  • Stream
hELLO · Designed By 정상우.v4.2.0
깃짱
[Sunny Braille] 팀 합류와 동시에 타이거 팀이 되다..! 장고 코드를 스프링 코드로 바꿔주세요!: Mathpix API 사용해서 수학문제 사진에서 OCR 처리하기 (수식 인식하기)
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.