Skip to content

Commit 3cd055c

Browse files
feat(ocr): allow users to avoid bounds.
1 parent 2de0249 commit 3cd055c

9 files changed

Lines changed: 40 additions & 80 deletions

File tree

jigsawstack/vision.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ class VOCRParams(TypedDict):
154154
"""
155155
High fidelity word-level bounding boxes within complex documents. Default: false.
156156
"""
157+
return_bounds: NotRequired[bool]
158+
"""
159+
Include line and word level bounding box coordinates. When false, the coordinates are omitted but the text and confidence are still returned. Default: true.
160+
"""
157161

158162

159163
class Word(TypedDict):

tests/test_audio.py

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -131,26 +131,20 @@
131131
class TestAudioSync:
132132
"""Test synchronous audio speech-to-text methods"""
133133

134-
@pytest.mark.parametrize(
135-
"test_case", TEST_CASES, ids=[tc["name"] for tc in TEST_CASES]
136-
)
134+
@pytest.mark.parametrize("test_case", TEST_CASES, ids=[tc["name"] for tc in TEST_CASES])
137135
def test_speech_to_text(self, test_case):
138136
"""Test synchronous speech-to-text with various inputs"""
139137
try:
140138
if test_case.get("blob"):
141139
# Download audio content
142140
blob_content = requests.get(test_case["blob"]).content
143-
result = jigsaw.audio.speech_to_text(
144-
blob_content, test_case.get("options", {})
145-
)
141+
result = jigsaw.audio.speech_to_text(blob_content, test_case.get("options", {}))
146142
else:
147143
# Use params directly
148144
result = jigsaw.audio.speech_to_text(test_case["params"])
149145
# Verify response structure
150146
assert result["success"]
151-
assert result.get("text", None) is not None and isinstance(
152-
result["text"], str
153-
)
147+
assert result.get("text", None) is not None and isinstance(result["text"], str)
154148

155149
# Check for chunks
156150
if result.get("chunks", None):
@@ -172,9 +166,7 @@ def test_speech_to_text_webhook(self, test_case):
172166
if test_case.get("blob"):
173167
# Download audio content
174168
blob_content = requests.get(test_case["blob"]).content
175-
result = jigsaw.audio.speech_to_text(
176-
blob_content, test_case.get("options", {})
177-
)
169+
result = jigsaw.audio.speech_to_text(blob_content, test_case.get("options", {}))
178170
else:
179171
# Use params directly
180172
result = jigsaw.audio.speech_to_text(test_case["params"])
@@ -189,9 +181,7 @@ def test_speech_to_text_webhook(self, test_case):
189181
class TestAudioAsync:
190182
"""Test asynchronous audio speech-to-text methods"""
191183

192-
@pytest.mark.parametrize(
193-
"test_case", TEST_CASES, ids=[tc["name"] for tc in TEST_CASES]
194-
)
184+
@pytest.mark.parametrize("test_case", TEST_CASES, ids=[tc["name"] for tc in TEST_CASES])
195185
@pytest.mark.asyncio
196186
async def test_speech_to_text_async(self, test_case):
197187
"""Test asynchronous speech-to-text with various inputs"""
@@ -208,9 +198,7 @@ async def test_speech_to_text_async(self, test_case):
208198

209199
# Verify response structure
210200
assert result["success"]
211-
assert result.get("text", None) is not None and isinstance(
212-
result["text"], str
213-
)
201+
assert result.get("text", None) is not None and isinstance(result["text"], str)
214202

215203
# Check for chunks
216204
if result.get("chunks", None):
@@ -220,9 +208,7 @@ async def test_speech_to_text_async(self, test_case):
220208
if result.get("speakers", None):
221209
assert isinstance(result["speakers"], list)
222210
except JigsawStackError as e:
223-
pytest.fail(
224-
f"Unexpected JigsawStackError in async {test_case['name']}: {e}"
225-
)
211+
pytest.fail(f"Unexpected JigsawStackError in async {test_case['name']}: {e}")
226212

227213
@pytest.mark.parametrize(
228214
"test_case", WEBHOOK_TEST_CASES, ids=[tc["name"] for tc in WEBHOOK_TEST_CASES]
@@ -248,6 +234,4 @@ async def test_speech_to_text_webhook_async(self, test_case):
248234

249235
except JigsawStackError as e:
250236
# Webhook URLs might fail if invalid
251-
print(
252-
f"Expected possible error for async webhook test {test_case['name']}: {e}"
253-
)
237+
print(f"Expected possible error for async webhook test {test_case['name']}: {e}")

tests/test_embedding.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@
2828
headers={"x-jigsaw-skip-cache": "true"},
2929
)
3030

31-
SAMPLE_TEXT = "The quick brown fox jumps over the lazy dog. This is a sample text for embedding generation."
31+
SAMPLE_TEXT = (
32+
"The quick brown fox jumps over the lazy dog. This is a sample text for embedding generation."
33+
)
3234
SAMPLE_IMAGE_URL = "https://images.unsplash.com/photo-1542931287-023b922fa89b?q=80&w=2574&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
3335
SAMPLE_AUDIO_URL = "https://jigsawstack.com/preview/stt-example.wav"
34-
SAMPLE_PDF_URL = (
35-
"https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
36-
)
36+
SAMPLE_PDF_URL = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
3737

3838
# Test cases for Embedding V2
3939
EMBEDDING_V2_TEST_CASES = [

tests/test_file_store.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,7 @@ class TestFileStoreAsync:
130130
async def test_file_upload_async(self, test_case):
131131
"""Test asynchronous file upload with various options"""
132132
try:
133-
result = await async_jigsaw.store.upload(
134-
test_case["file"], test_case["options"]
135-
)
133+
result = await async_jigsaw.store.upload(test_case["file"], test_case["options"])
136134

137135
print(f"Async upload test {test_case['name']}: {result}")
138136
assert result.get("key") is not None
@@ -147,9 +145,7 @@ async def test_file_upload_async(self, test_case):
147145
self.uploaded_keys.append(result["key"])
148146

149147
except JigsawStackError as e:
150-
pytest.fail(
151-
f"Unexpected JigsawStackError in async {test_case['name']}: {e}"
152-
)
148+
pytest.fail(f"Unexpected JigsawStackError in async {test_case['name']}: {e}")
153149

154150
@pytest.mark.asyncio
155151
async def test_file_get_async(self):

tests/test_object_detection.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
headers={"x-jigsaw-skip-cache": "true"},
3131
)
3232

33-
IMAGE_URL = "https://rogilvkqloanxtvjfrkm.supabase.co/storage/v1/object/public/demo/Collabo%201080x842.jpg"
33+
IMAGE_URL = (
34+
"https://rogilvkqloanxtvjfrkm.supabase.co/storage/v1/object/public/demo/Collabo%201080x842.jpg"
35+
)
3436

3537
TEST_CASES = [
3638
{
@@ -114,9 +116,7 @@ def test_object_detection(self, test_case):
114116
if test_case.get("blob"):
115117
# Download blob content
116118
blob_content = requests.get(test_case["blob"]).content
117-
result = jigsaw.vision.object_detection(
118-
blob_content, test_case.get("options", {})
119-
)
119+
result = jigsaw.vision.object_detection(blob_content, test_case.get("options", {}))
120120
else:
121121
# Use params directly
122122
result = jigsaw.vision.object_detection(test_case["params"])

tests/test_prediction.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,7 @@ def generate_dates(start_date, num_days):
5959
{
6060
"name": "seasonal_pattern",
6161
"params": {
62-
"dataset": [
63-
{"date": dates[i], "value": 100 + (50 * (i % 7))} for i in range(21)
64-
],
62+
"dataset": [{"date": dates[i], "value": 100 + (50 * (i % 7))} for i in range(21)],
6563
"steps": 7,
6664
},
6765
},
@@ -75,9 +73,7 @@ def generate_dates(start_date, num_days):
7573
{
7674
"name": "large_dataset_prediction",
7775
"params": {
78-
"dataset": [
79-
{"date": dates[i], "value": 1000 + (i * 20)} for i in range(30)
80-
],
76+
"dataset": [{"date": dates[i], "value": 1000 + (i * 20)} for i in range(30)],
8177
"steps": 10,
8278
},
8379
},

tests/test_translate.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,7 @@ def test_translate_text(self, test_case):
9494
# Check if the response structure matches the input
9595
if isinstance(test_case["params"]["text"], list):
9696
assert isinstance(result["translated_text"], list)
97-
assert len(result["translated_text"]) == len(
98-
test_case["params"]["text"]
99-
)
97+
assert len(result["translated_text"]) == len(test_case["params"]["text"])
10098
else:
10199
assert isinstance(result["translated_text"], str)
102100

@@ -123,9 +121,7 @@ async def test_translate_text_async(self, test_case):
123121
# Check if the response structure matches the input
124122
if isinstance(test_case["params"]["text"], list):
125123
assert isinstance(result["translated_text"], list)
126-
assert len(result["translated_text"]) == len(
127-
test_case["params"]["text"]
128-
)
124+
assert len(result["translated_text"]) == len(test_case["params"]["text"])
129125
else:
130126
assert isinstance(result["translated_text"], str)
131127

tests/test_validate.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@
2929
)
3030

3131
# Sample URLs for NSFW testing
32-
SAFE_IMAGE_URL = (
33-
"https://images.unsplash.com/photo-1506905925346-21bda4d32df4?q=80&w=2070"
34-
)
32+
SAFE_IMAGE_URL = "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?q=80&w=2070"
3533
POTENTIALLY_NSFW_URL = "https://images.unsplash.com/photo-1512310604669-443f26c35f52?q=80&w=868&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
3634

3735
# Profanity Test Cases
@@ -240,9 +238,7 @@ async def test_nsfw_check_blob_async(self, test_case):
240238
try:
241239
# Download blob content
242240
blob_content = requests.get(test_case["blob_url"]).content
243-
result = await async_jigsaw.validate.nsfw(
244-
blob_content, test_case["options"]
245-
)
241+
result = await async_jigsaw.validate.nsfw(blob_content, test_case["options"])
246242

247243
assert result["success"]
248244
assert "nsfw" in result

tests/test_vocr.py

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,7 @@ def test_vocr(self, test_case):
157157
except JigsawStackError as e:
158158
pytest.fail(f"Unexpected JigsawStackError in {test_case['name']}: {e}")
159159

160-
@pytest.mark.parametrize(
161-
"test_case", pdf_test_cases, ids=[tc["name"] for tc in pdf_test_cases]
162-
)
160+
@pytest.mark.parametrize("test_case", pdf_test_cases, ids=[tc["name"] for tc in pdf_test_cases])
163161
def test_vocr_pdf(self, test_case):
164162
"""Test synchronous VOCR with PDF inputs"""
165163
try:
@@ -177,15 +175,13 @@ def test_vocr_pdf(self, test_case):
177175
assert "context" in result
178176
assert "total_pages" in result
179177

180-
if test_case.get("params", {}).get("page_range") or test_case.get(
181-
"options", {}
182-
).get("page_range"):
178+
if test_case.get("params", {}).get("page_range") or test_case.get("options", {}).get(
179+
"page_range"
180+
):
183181
assert "page_range" in result
184182
assert isinstance(result["page_range"], list)
185183

186-
logger.info(
187-
f"Test {test_case['name']}: total_pages={result.get('total_pages')}"
188-
)
184+
logger.info(f"Test {test_case['name']}: total_pages={result.get('total_pages')}")
189185

190186
except JigsawStackError as e:
191187
pytest.fail(f"Unexpected JigsawStackError in {test_case['name']}: {e}")
@@ -207,9 +203,7 @@ async def test_vocr_async(self, test_case):
207203
if test_case.get("blob"):
208204
# Download blob content
209205
blob_content = requests.get(test_case["blob"]).content
210-
result = await async_jigsaw.vision.vocr(
211-
blob_content, test_case.get("options", {})
212-
)
206+
result = await async_jigsaw.vision.vocr(blob_content, test_case.get("options", {}))
213207
else:
214208
# Use params directly
215209
result = await async_jigsaw.vision.vocr(test_case["params"])
@@ -236,19 +230,15 @@ async def test_vocr_async(self, test_case):
236230
except JigsawStackError as e:
237231
pytest.fail(f"Unexpected JigsawStackError in {test_case['name']}: {e}")
238232

239-
@pytest.mark.parametrize(
240-
"test_case", pdf_test_cases, ids=[tc["name"] for tc in pdf_test_cases]
241-
)
233+
@pytest.mark.parametrize("test_case", pdf_test_cases, ids=[tc["name"] for tc in pdf_test_cases])
242234
@pytest.mark.asyncio
243235
async def test_vocr_pdf_async(self, test_case):
244236
"""Test asynchronous VOCR with PDF inputs"""
245237
try:
246238
if test_case.get("blob"):
247239
# Download blob content
248240
blob_content = requests.get(test_case["blob"]).content
249-
result = await async_jigsaw.vision.vocr(
250-
blob_content, test_case.get("options", {})
251-
)
241+
result = await async_jigsaw.vision.vocr(blob_content, test_case.get("options", {}))
252242
else:
253243
# Use params directly
254244
result = await async_jigsaw.vision.vocr(test_case["params"])
@@ -262,15 +252,13 @@ async def test_vocr_pdf_async(self, test_case):
262252
assert "total_pages" in result # PDF specific
263253

264254
# Check if page_range is in response when requested
265-
if test_case.get("params", {}).get("page_range") or test_case.get(
266-
"options", {}
267-
).get("page_range"):
255+
if test_case.get("params", {}).get("page_range") or test_case.get("options", {}).get(
256+
"page_range"
257+
):
268258
assert "page_range" in result
269259
assert isinstance(result["page_range"], list)
270260

271-
logger.info(
272-
f"Test {test_case['name']}: total_pages={result.get('total_pages')}"
273-
)
261+
logger.info(f"Test {test_case['name']}: total_pages={result.get('total_pages')}")
274262

275263
except JigsawStackError as e:
276264
pytest.fail(f"Unexpected JigsawStackError in {test_case['name']}: {e}")

0 commit comments

Comments
 (0)