fix: prefer aggregate OCR text
This commit is contained in:
@@ -349,3 +349,4 @@ export namespace main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -264,6 +264,9 @@ func parseOCRText(data []byte) (string, error) {
|
|||||||
if msg := responseError(root); msg != "" {
|
if msg := responseError(root); msg != "" {
|
||||||
return "", errors.New(msg)
|
return "", errors.New(msg)
|
||||||
}
|
}
|
||||||
|
if text := normalizeOCRText(extractOverallOCRText(root, "")); text != "" {
|
||||||
|
return text, nil
|
||||||
|
}
|
||||||
parts := collectOCRParts(root, "")
|
parts := collectOCRParts(root, "")
|
||||||
if len(parts) == 0 {
|
if len(parts) == 0 {
|
||||||
return "", fmt.Errorf("no text found")
|
return "", fmt.Errorf("no text found")
|
||||||
@@ -312,6 +315,62 @@ func responseError(v any) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractOverallOCRText(v any, parentKey string) string {
|
||||||
|
switch value := v.(type) {
|
||||||
|
case string:
|
||||||
|
text := strings.TrimSpace(value)
|
||||||
|
if text == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if looksLikeJSON(text) {
|
||||||
|
var nested any
|
||||||
|
if err := json.Unmarshal([]byte(text), &nested); err == nil {
|
||||||
|
if nestedText := extractOverallOCRText(nested, parentKey); nestedText != "" {
|
||||||
|
return nestedText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isAggregateContainerKey(parentKey) {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
case map[string]any:
|
||||||
|
for _, key := range aggregateTextKeys() {
|
||||||
|
if child, ok := value[key]; ok {
|
||||||
|
if text := extractOverallOCRText(child, key); text != "" {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, key := range responseContainerKeys() {
|
||||||
|
if child, ok := value[key]; ok {
|
||||||
|
if text := extractOverallOCRText(child, key); text != "" {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keys := make([]string, 0, len(value))
|
||||||
|
for key := range value {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, key := range keys {
|
||||||
|
if isAggregateTextKey(key) ||
|
||||||
|
isResponseContainerKey(key) ||
|
||||||
|
isBlockContainerKey(key) ||
|
||||||
|
isMetadataKey(key) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if text := extractOverallOCRText(value[key], key); text != "" {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func collectOCRParts(v any, parentKey string) []string {
|
func collectOCRParts(v any, parentKey string) []string {
|
||||||
switch value := v.(type) {
|
switch value := v.(type) {
|
||||||
case string:
|
case string:
|
||||||
@@ -359,6 +418,36 @@ func collectOCRParts(v any, parentKey string) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func aggregateTextKeys() []string {
|
||||||
|
return []string{
|
||||||
|
"content",
|
||||||
|
"Content",
|
||||||
|
"text",
|
||||||
|
"Text",
|
||||||
|
"full_text",
|
||||||
|
"FullText",
|
||||||
|
"fullText",
|
||||||
|
"plain_text",
|
||||||
|
"PlainText",
|
||||||
|
"plainText",
|
||||||
|
"recognized_text",
|
||||||
|
"RecognizedText",
|
||||||
|
"recognizedText",
|
||||||
|
"ocr_text",
|
||||||
|
"OCRText",
|
||||||
|
"ocrText",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func responseContainerKeys() []string {
|
||||||
|
return []string{
|
||||||
|
"Data",
|
||||||
|
"data",
|
||||||
|
"Result",
|
||||||
|
"result",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func priorityOCRKeys() []string {
|
func priorityOCRKeys() []string {
|
||||||
return []string{
|
return []string{
|
||||||
"Data",
|
"Data",
|
||||||
@@ -394,6 +483,38 @@ func priorityOCRKeys() []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isAggregateTextKey(key string) bool {
|
||||||
|
for _, item := range aggregateTextKeys() {
|
||||||
|
if item == key {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAggregateContainerKey(key string) bool {
|
||||||
|
return isAggregateTextKey(key) || isResponseContainerKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isResponseContainerKey(key string) bool {
|
||||||
|
for _, item := range responseContainerKeys() {
|
||||||
|
if item == key {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBlockContainerKey(key string) bool {
|
||||||
|
switch normalizeKey(key) {
|
||||||
|
case "wordsresult", "prismwordsinfo", "prismwords", "ocrinfos", "items",
|
||||||
|
"blocks", "regions", "words", "linetexts", "lines", "cells":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func isPriorityOCRKey(key string) bool {
|
func isPriorityOCRKey(key string) bool {
|
||||||
for _, item := range priorityOCRKeys() {
|
for _, item := range priorityOCRKeys() {
|
||||||
if item == key {
|
if item == key {
|
||||||
@@ -459,6 +580,18 @@ func dedupeNonEmpty(parts []string) []string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeOCRText(text string) string {
|
||||||
|
lines := strings.Split(strings.TrimSpace(text), "\n")
|
||||||
|
out := make([]string, 0, len(lines))
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line != "" {
|
||||||
|
out = append(out, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(out, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
func looksLikeJSON(text string) bool {
|
func looksLikeJSON(text string) bool {
|
||||||
return strings.HasPrefix(text, "{") || strings.HasPrefix(text, "[")
|
return strings.HasPrefix(text, "{") || strings.HasPrefix(text, "[")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,6 +142,33 @@ func TestParseOCRTextSupportsWordsResult(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseOCRTextPrefersOverallContentOverBlocks(t *testing.T) {
|
||||||
|
text, err := parseOCRText([]byte(`{
|
||||||
|
"Data": "{\"content\":\"整体识别文本\",\"prism_wordsInfo\":[{\"word\":\"整体\"},{\"word\":\"识别\"},{\"word\":\"文本\"}]}"
|
||||||
|
}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse ocr text: %v", err)
|
||||||
|
}
|
||||||
|
if text != "整体识别文本" {
|
||||||
|
t.Fatalf("expected overall content only, got %q", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseOCRTextPrefersResultTextOverWordsResult(t *testing.T) {
|
||||||
|
text, err := parseOCRText([]byte(`{
|
||||||
|
"Result": {
|
||||||
|
"text": "hello world",
|
||||||
|
"words_result": [{"words":"hello"},{"words":"world"}]
|
||||||
|
}
|
||||||
|
}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse ocr text: %v", err)
|
||||||
|
}
|
||||||
|
if text != "hello world" {
|
||||||
|
t.Fatalf("expected result text only, got %q", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseOCRTextReturnsProviderError(t *testing.T) {
|
func TestParseOCRTextReturnsProviderError(t *testing.T) {
|
||||||
_, err := parseOCRText([]byte(`{"ResponseMetadata":{"Error":{"Code":"BadRequest","Message":"bad image"}}}`))
|
_, err := parseOCRText([]byte(`{"ResponseMetadata":{"Error":{"Code":"BadRequest","Message":"bad image"}}}`))
|
||||||
if err == nil || !strings.Contains(err.Error(), "bad image") {
|
if err == nil || !strings.Contains(err.Error(), "bad image") {
|
||||||
|
|||||||
Reference in New Issue
Block a user