doctra 0.4.0__py3-none-any.whl → 0.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- doctra/cli/main.py +5 -12
- doctra/cli/utils.py +2 -3
- doctra/engines/image_restoration/docres_engine.py +6 -11
- doctra/engines/vlm/outlines_types.py +13 -9
- doctra/engines/vlm/service.py +4 -2
- doctra/exporters/excel_writer.py +89 -0
- doctra/exporters/html_writer.py +206 -1
- doctra/parsers/enhanced_pdf_parser.py +124 -31
- doctra/parsers/structured_pdf_parser.py +58 -15
- doctra/parsers/table_chart_extractor.py +290 -284
- doctra/ui/app.py +39 -960
- doctra/ui/docres_ui.py +338 -0
- doctra/ui/docres_wrapper.py +120 -0
- doctra/ui/enhanced_parser_ui.py +483 -0
- doctra/ui/full_parse_ui.py +539 -0
- doctra/ui/tables_charts_ui.py +445 -0
- doctra/ui/ui_helpers.py +435 -0
- doctra/utils/progress.py +7 -7
- doctra/utils/structured_utils.py +5 -2
- doctra/version.py +1 -1
- {doctra-0.4.0.dist-info → doctra-0.4.2.dist-info}/METADATA +1 -1
- {doctra-0.4.0.dist-info → doctra-0.4.2.dist-info}/RECORD +25 -19
- {doctra-0.4.0.dist-info → doctra-0.4.2.dist-info}/WHEEL +0 -0
- {doctra-0.4.0.dist-info → doctra-0.4.2.dist-info}/licenses/LICENSE +0 -0
- {doctra-0.4.0.dist-info → doctra-0.4.2.dist-info}/top_level.txt +0 -0
doctra/ui/docres_ui.py
ADDED
@@ -0,0 +1,338 @@
|
|
1
|
+
"""
|
2
|
+
DocRes Image Restoration UI Module
|
3
|
+
|
4
|
+
This module contains all functionality for the DocRes image restoration tab in the Doctra Gradio interface.
|
5
|
+
It handles PDF restoration, before/after comparison, and enhanced file management.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import tempfile
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import Tuple, List, Optional
|
11
|
+
|
12
|
+
import gradio as gr
|
13
|
+
|
14
|
+
from doctra.ui.docres_wrapper import DocResUIWrapper
|
15
|
+
from doctra.utils.pdf_io import render_pdf_to_images
|
16
|
+
|
17
|
+
|
18
|
+
def render_pdf_pages(pdf_path: str, max_pages: int = 10) -> Tuple[List[str], List[str]]:
|
19
|
+
"""
|
20
|
+
Render PDF pages to images for display.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
pdf_path: Path to PDF file
|
24
|
+
max_pages: Maximum number of pages to render
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
Tuple of (image_paths, page_options)
|
28
|
+
"""
|
29
|
+
if not pdf_path or not Path(pdf_path).exists():
|
30
|
+
return [], []
|
31
|
+
|
32
|
+
try:
|
33
|
+
# render_pdf_to_images returns (pil_image, width, height) tuples
|
34
|
+
image_tuples = render_pdf_to_images(pdf_path)
|
35
|
+
|
36
|
+
# Limit to max_pages if specified
|
37
|
+
if max_pages and len(image_tuples) > max_pages:
|
38
|
+
image_tuples = image_tuples[:max_pages]
|
39
|
+
|
40
|
+
# Convert PIL images to file paths for display
|
41
|
+
images = []
|
42
|
+
page_options = []
|
43
|
+
|
44
|
+
for i, (pil_image, width, height) in enumerate(image_tuples):
|
45
|
+
# Save PIL image to temporary file
|
46
|
+
temp_file = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
|
47
|
+
pil_image.save(temp_file.name, 'PNG')
|
48
|
+
images.append(temp_file.name)
|
49
|
+
page_options.append(f"Page {i+1}")
|
50
|
+
|
51
|
+
return images, page_options
|
52
|
+
except Exception as e:
|
53
|
+
print(f"Error rendering PDF pages: {e}")
|
54
|
+
return [], []
|
55
|
+
|
56
|
+
|
57
|
+
def update_docres_page_selector(original_pdf: str, enhanced_pdf: str) -> Tuple[gr.Dropdown, List[str], List[str], str, str, Optional[str], Optional[str]]:
|
58
|
+
"""
|
59
|
+
Update single page selector when PDFs are loaded.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
original_pdf: Path to original PDF file
|
63
|
+
enhanced_pdf: Path to enhanced PDF file
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
Tuple of (dropdown, original_pages, enhanced_pages, original_pdf_path, enhanced_pdf_path, first_original_image, first_enhanced_image)
|
67
|
+
"""
|
68
|
+
original_pages, original_options = render_pdf_pages(original_pdf) if original_pdf else ([], [])
|
69
|
+
enhanced_pages, enhanced_options = render_pdf_pages(enhanced_pdf) if enhanced_pdf else ([], [])
|
70
|
+
|
71
|
+
# Use the same page options for the single selector (use the longer list)
|
72
|
+
if len(original_options) >= len(enhanced_options):
|
73
|
+
common_options = original_options
|
74
|
+
else:
|
75
|
+
common_options = enhanced_options
|
76
|
+
|
77
|
+
# Set default to first page if available
|
78
|
+
default_page = common_options[0] if common_options else None
|
79
|
+
|
80
|
+
return (
|
81
|
+
gr.Dropdown(choices=common_options, value=default_page, visible=bool(common_options)),
|
82
|
+
original_pages,
|
83
|
+
enhanced_pages,
|
84
|
+
original_pdf or "",
|
85
|
+
enhanced_pdf or "",
|
86
|
+
original_pages[0] if original_pages else None, # First page image
|
87
|
+
enhanced_pages[0] if enhanced_pages else None # First page image
|
88
|
+
)
|
89
|
+
|
90
|
+
|
91
|
+
def display_docres_page(page_selector: str, pages_data: List[str], pdf_path: str) -> Optional[str]:
|
92
|
+
"""
|
93
|
+
Display selected page from PDF.
|
94
|
+
|
95
|
+
Args:
|
96
|
+
page_selector: Selected page identifier
|
97
|
+
pages_data: List of page image paths
|
98
|
+
pdf_path: Path to PDF file
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
Path to selected page image
|
102
|
+
"""
|
103
|
+
if not page_selector or not pages_data or not pdf_path:
|
104
|
+
return None
|
105
|
+
|
106
|
+
try:
|
107
|
+
page_index = int(page_selector.split()[1]) - 1 # "Page 1" -> index 0
|
108
|
+
if 0 <= page_index < len(pages_data):
|
109
|
+
return pages_data[page_index]
|
110
|
+
except (ValueError, IndexError):
|
111
|
+
pass
|
112
|
+
|
113
|
+
return None
|
114
|
+
|
115
|
+
|
116
|
+
def sync_page_changes(
|
117
|
+
page_selector: str,
|
118
|
+
original_pages: List[str],
|
119
|
+
enhanced_pages: List[str],
|
120
|
+
original_pdf_path: str,
|
121
|
+
enhanced_pdf_path: str
|
122
|
+
) -> Tuple[Optional[str], Optional[str]]:
|
123
|
+
"""
|
124
|
+
Synchronize page changes between original and enhanced PDFs.
|
125
|
+
|
126
|
+
Args:
|
127
|
+
page_selector: Selected page identifier
|
128
|
+
original_pages: List of original page image paths
|
129
|
+
enhanced_pages: List of enhanced page image paths
|
130
|
+
original_pdf_path: Path to original PDF
|
131
|
+
enhanced_pdf_path: Path to enhanced PDF
|
132
|
+
|
133
|
+
Returns:
|
134
|
+
Tuple of (original_page_image, enhanced_page_image)
|
135
|
+
"""
|
136
|
+
if not page_selector:
|
137
|
+
return None, None
|
138
|
+
|
139
|
+
# Get the page index
|
140
|
+
try:
|
141
|
+
page_index = int(page_selector.split()[1]) - 1 # "Page 1" -> index 0
|
142
|
+
except (ValueError, IndexError):
|
143
|
+
return None, None
|
144
|
+
|
145
|
+
# Get the corresponding page from each PDF
|
146
|
+
original_page = None
|
147
|
+
enhanced_page = None
|
148
|
+
|
149
|
+
if original_pages and 0 <= page_index < len(original_pages):
|
150
|
+
original_page = original_pages[page_index]
|
151
|
+
|
152
|
+
if enhanced_pages and 0 <= page_index < len(enhanced_pages):
|
153
|
+
enhanced_page = enhanced_pages[page_index]
|
154
|
+
|
155
|
+
return original_page, enhanced_page
|
156
|
+
|
157
|
+
|
158
|
+
def run_docres_restoration(
|
159
|
+
pdf_file: str,
|
160
|
+
task: str,
|
161
|
+
device: str,
|
162
|
+
dpi: int,
|
163
|
+
save_enhanced: bool,
|
164
|
+
save_images: bool
|
165
|
+
) -> Tuple[str, Optional[str], Optional[str], Optional[dict], List[str]]:
|
166
|
+
"""
|
167
|
+
Run DocRes image restoration on PDF.
|
168
|
+
|
169
|
+
Args:
|
170
|
+
pdf_file: Path to input PDF file
|
171
|
+
task: Restoration task type
|
172
|
+
device: Device to use for processing
|
173
|
+
dpi: DPI for processing
|
174
|
+
save_enhanced: Whether to save enhanced PDF
|
175
|
+
save_images: Whether to save enhanced images
|
176
|
+
|
177
|
+
Returns:
|
178
|
+
Tuple of (status_message, original_pdf_path, enhanced_pdf_path, metadata, file_paths)
|
179
|
+
"""
|
180
|
+
if not pdf_file:
|
181
|
+
return ("No file provided.", None, [], None, [])
|
182
|
+
|
183
|
+
try:
|
184
|
+
# Initialize DocRes engine
|
185
|
+
device_str = None if device == "auto" else device
|
186
|
+
docres = DocResUIWrapper(device=device_str)
|
187
|
+
|
188
|
+
# Extract filename
|
189
|
+
original_filename = Path(pdf_file).stem
|
190
|
+
|
191
|
+
# Create output directory
|
192
|
+
output_dir = Path("outputs") / f"{original_filename}_docres"
|
193
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
194
|
+
|
195
|
+
# Run DocRes restoration
|
196
|
+
enhanced_pdf_path = output_dir / f"{original_filename}_enhanced.pdf"
|
197
|
+
docres.restore_pdf(
|
198
|
+
pdf_path=pdf_file,
|
199
|
+
output_path=str(enhanced_pdf_path),
|
200
|
+
task=task,
|
201
|
+
dpi=dpi
|
202
|
+
)
|
203
|
+
|
204
|
+
# Prepare outputs
|
205
|
+
file_paths = []
|
206
|
+
|
207
|
+
if save_enhanced and enhanced_pdf_path.exists():
|
208
|
+
file_paths.append(str(enhanced_pdf_path))
|
209
|
+
|
210
|
+
if save_images:
|
211
|
+
# Look for enhanced images
|
212
|
+
images_dir = output_dir / "enhanced_images"
|
213
|
+
if images_dir.exists():
|
214
|
+
for img_path in sorted(images_dir.glob("*.jpg")):
|
215
|
+
file_paths.append(str(img_path))
|
216
|
+
|
217
|
+
# Create metadata
|
218
|
+
metadata = {
|
219
|
+
"task": task,
|
220
|
+
"device": str(docres.device),
|
221
|
+
"dpi": dpi,
|
222
|
+
"original_file": pdf_file,
|
223
|
+
"enhanced_file": str(enhanced_pdf_path) if enhanced_pdf_path.exists() else None,
|
224
|
+
"output_directory": str(output_dir)
|
225
|
+
}
|
226
|
+
|
227
|
+
status_msg = f"✅ DocRes restoration completed successfully!\n📁 Output directory: {output_dir}"
|
228
|
+
|
229
|
+
enhanced_pdf_file = str(enhanced_pdf_path) if enhanced_pdf_path.exists() else None
|
230
|
+
return (status_msg, pdf_file, enhanced_pdf_file, metadata, file_paths)
|
231
|
+
|
232
|
+
except Exception as e:
|
233
|
+
error_msg = f"❌ DocRes restoration failed: {str(e)}"
|
234
|
+
return (error_msg, None, None, None, [])
|
235
|
+
|
236
|
+
|
237
|
+
def create_docres_tab() -> Tuple[gr.Tab, dict]:
|
238
|
+
"""
|
239
|
+
Create the DocRes Image Restoration tab with all its components and functionality.
|
240
|
+
|
241
|
+
Returns:
|
242
|
+
Tuple of (tab_component, state_variables_dict)
|
243
|
+
"""
|
244
|
+
with gr.Tab("DocRes Image Restoration") as tab:
|
245
|
+
# Input controls
|
246
|
+
with gr.Row():
|
247
|
+
pdf_docres = gr.File(file_types=[".pdf"], label="PDF")
|
248
|
+
docres_task_standalone = gr.Dropdown(
|
249
|
+
["appearance", "dewarping", "deshadowing", "deblurring", "binarization", "end2end"],
|
250
|
+
value="appearance",
|
251
|
+
label="Restoration Task"
|
252
|
+
)
|
253
|
+
docres_device_standalone = gr.Dropdown(
|
254
|
+
["auto", "cuda", "cpu"],
|
255
|
+
value="auto",
|
256
|
+
label="Device"
|
257
|
+
)
|
258
|
+
|
259
|
+
# Additional settings
|
260
|
+
with gr.Row():
|
261
|
+
docres_dpi = gr.Slider(100, 400, value=200, step=10, label="DPI")
|
262
|
+
docres_save_enhanced = gr.Checkbox(label="Save Enhanced PDF", value=True)
|
263
|
+
docres_save_images = gr.Checkbox(label="Save Enhanced Images", value=True)
|
264
|
+
|
265
|
+
# Action button
|
266
|
+
run_docres_btn = gr.Button("▶ Run DocRes Restoration", variant="primary")
|
267
|
+
docres_status = gr.Textbox(label="Status", elem_classes=["status-ok"])
|
268
|
+
|
269
|
+
# Single page selector for both PDFs
|
270
|
+
with gr.Row():
|
271
|
+
docres_page_selector = gr.Dropdown(label="Select Page", interactive=True, visible=False)
|
272
|
+
|
273
|
+
# Before/After PDF comparison
|
274
|
+
with gr.Row():
|
275
|
+
with gr.Column():
|
276
|
+
gr.Markdown("### 📄 Original PDF")
|
277
|
+
docres_original_pdf = gr.File(label="Original PDF File", interactive=False, visible=False)
|
278
|
+
docres_original_page_image = gr.Image(label="Original PDF Page", interactive=False, height=800)
|
279
|
+
with gr.Column():
|
280
|
+
gr.Markdown("### ✨ Enhanced PDF")
|
281
|
+
docres_enhanced_pdf = gr.File(label="Enhanced PDF File", interactive=False, visible=False)
|
282
|
+
docres_enhanced_page_image = gr.Image(label="Enhanced PDF Page", interactive=False, height=800)
|
283
|
+
|
284
|
+
# DocRes outputs
|
285
|
+
with gr.Row():
|
286
|
+
with gr.Column():
|
287
|
+
docres_metadata = gr.JSON(label="Restoration Metadata", visible=False)
|
288
|
+
|
289
|
+
docres_files_out = gr.Files(label="Download enhanced files")
|
290
|
+
|
291
|
+
# State variables for PDF page data
|
292
|
+
docres_original_pages_state = gr.State([])
|
293
|
+
docres_enhanced_pages_state = gr.State([])
|
294
|
+
docres_original_pdf_path_state = gr.State("")
|
295
|
+
docres_enhanced_pdf_path_state = gr.State("")
|
296
|
+
|
297
|
+
# Event handlers
|
298
|
+
run_docres_btn.click(
|
299
|
+
fn=run_docres_restoration,
|
300
|
+
inputs=[pdf_docres, docres_task_standalone, docres_device_standalone, docres_dpi, docres_save_enhanced, docres_save_images],
|
301
|
+
outputs=[docres_status, docres_original_pdf, docres_enhanced_pdf, docres_metadata, docres_files_out]
|
302
|
+
).then(
|
303
|
+
fn=update_docres_page_selector,
|
304
|
+
inputs=[docres_original_pdf, docres_enhanced_pdf],
|
305
|
+
outputs=[docres_page_selector, docres_original_pages_state, docres_enhanced_pages_state, docres_original_pdf_path_state, docres_enhanced_pdf_path_state, docres_original_page_image, docres_enhanced_page_image]
|
306
|
+
)
|
307
|
+
|
308
|
+
# Handle single page selector changes
|
309
|
+
docres_page_selector.change(
|
310
|
+
fn=sync_page_changes,
|
311
|
+
inputs=[docres_page_selector, docres_original_pages_state, docres_enhanced_pages_state, docres_original_pdf_path_state, docres_enhanced_pdf_path_state],
|
312
|
+
outputs=[docres_original_page_image, docres_enhanced_page_image]
|
313
|
+
)
|
314
|
+
|
315
|
+
# Return state variables for external access
|
316
|
+
state_vars = {
|
317
|
+
'pdf_docres': pdf_docres,
|
318
|
+
'docres_task_standalone': docres_task_standalone,
|
319
|
+
'docres_device_standalone': docres_device_standalone,
|
320
|
+
'docres_dpi': docres_dpi,
|
321
|
+
'docres_save_enhanced': docres_save_enhanced,
|
322
|
+
'docres_save_images': docres_save_images,
|
323
|
+
'run_docres_btn': run_docres_btn,
|
324
|
+
'docres_status': docres_status,
|
325
|
+
'docres_page_selector': docres_page_selector,
|
326
|
+
'docres_original_pdf': docres_original_pdf,
|
327
|
+
'docres_original_page_image': docres_original_page_image,
|
328
|
+
'docres_enhanced_pdf': docres_enhanced_pdf,
|
329
|
+
'docres_enhanced_page_image': docres_enhanced_page_image,
|
330
|
+
'docres_metadata': docres_metadata,
|
331
|
+
'docres_files_out': docres_files_out,
|
332
|
+
'docres_original_pages_state': docres_original_pages_state,
|
333
|
+
'docres_enhanced_pages_state': docres_enhanced_pages_state,
|
334
|
+
'docres_original_pdf_path_state': docres_original_pdf_path_state,
|
335
|
+
'docres_enhanced_pdf_path_state': docres_enhanced_pdf_path_state
|
336
|
+
}
|
337
|
+
|
338
|
+
return tab, state_vars
|
@@ -0,0 +1,120 @@
|
|
1
|
+
"""
|
2
|
+
DocRes wrapper for Gradio UI context
|
3
|
+
|
4
|
+
This module provides a wrapper around DocResEngine that handles
|
5
|
+
path issues when running in Gradio UI context.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import os
|
9
|
+
import tempfile
|
10
|
+
import shutil
|
11
|
+
from pathlib import Path
|
12
|
+
from typing import Optional, Union
|
13
|
+
import numpy as np
|
14
|
+
|
15
|
+
from doctra.engines.image_restoration.docres_engine import DocResEngine
|
16
|
+
|
17
|
+
|
18
|
+
class DocResUIWrapper:
|
19
|
+
"""
|
20
|
+
Wrapper for DocResEngine that handles path issues in Gradio UI context
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self, device: Optional[str] = None):
|
24
|
+
"""
|
25
|
+
Initialize the DocRes wrapper
|
26
|
+
|
27
|
+
Args:
|
28
|
+
device: Device to use ('cuda', 'cpu', or None for auto-detect)
|
29
|
+
"""
|
30
|
+
self.device = device
|
31
|
+
self._docres = None
|
32
|
+
self._setup_model_paths()
|
33
|
+
|
34
|
+
def _setup_model_paths(self):
|
35
|
+
"""Setup model paths to work in Gradio context"""
|
36
|
+
try:
|
37
|
+
# Initialize DocRes to download models
|
38
|
+
self._docres = DocResEngine(device=self.device)
|
39
|
+
|
40
|
+
# Get the actual model paths
|
41
|
+
mbd_path = self._docres.mbd_path
|
42
|
+
model_path = self._docres.model_path
|
43
|
+
|
44
|
+
# Get DocRes directory
|
45
|
+
current_dir = Path(__file__).parent.parent
|
46
|
+
docres_dir = current_dir / "third_party" / "docres"
|
47
|
+
|
48
|
+
# Create expected directory structure
|
49
|
+
expected_mbd_dir = docres_dir / "data" / "MBD" / "checkpoint"
|
50
|
+
expected_model_dir = docres_dir / "checkpoints"
|
51
|
+
|
52
|
+
expected_mbd_dir.mkdir(parents=True, exist_ok=True)
|
53
|
+
expected_model_dir.mkdir(parents=True, exist_ok=True)
|
54
|
+
|
55
|
+
# Copy model files to expected locations if they don't exist
|
56
|
+
expected_mbd_path = expected_mbd_dir / "mbd.pkl"
|
57
|
+
expected_model_path = expected_model_dir / "docres.pkl"
|
58
|
+
|
59
|
+
if not expected_mbd_path.exists() and Path(mbd_path).exists():
|
60
|
+
shutil.copy2(mbd_path, expected_mbd_path)
|
61
|
+
|
62
|
+
if not expected_model_path.exists() and Path(model_path).exists():
|
63
|
+
shutil.copy2(model_path, expected_model_path)
|
64
|
+
|
65
|
+
except Exception as e:
|
66
|
+
print(f"⚠️ DocRes setup warning: {e}")
|
67
|
+
# Continue anyway, the original paths might work
|
68
|
+
|
69
|
+
def restore_pdf(self, pdf_path: str, output_path: str, task: str = "appearance", dpi: int = 200) -> str:
|
70
|
+
"""
|
71
|
+
Restore a PDF using DocRes with proper path handling
|
72
|
+
|
73
|
+
Args:
|
74
|
+
pdf_path: Path to input PDF
|
75
|
+
output_path: Path to output enhanced PDF
|
76
|
+
task: Restoration task
|
77
|
+
dpi: DPI for processing
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
Path to enhanced PDF
|
81
|
+
"""
|
82
|
+
if self._docres is None:
|
83
|
+
raise RuntimeError("DocRes not properly initialized")
|
84
|
+
|
85
|
+
try:
|
86
|
+
# Use the original DocRes method
|
87
|
+
return self._docres.restore_pdf(pdf_path, output_path, task, dpi)
|
88
|
+
except Exception as e:
|
89
|
+
# If it fails due to path issues, try to fix them and retry once
|
90
|
+
if "No such file or directory" in str(e) and "mbd.pkl" in str(e):
|
91
|
+
print("🔧 Attempting to fix DocRes model paths...")
|
92
|
+
self._setup_model_paths()
|
93
|
+
return self._docres.restore_pdf(pdf_path, output_path, task, dpi)
|
94
|
+
else:
|
95
|
+
raise e
|
96
|
+
|
97
|
+
def restore_image(self, image: Union[str, np.ndarray], task: str = "appearance") -> tuple:
|
98
|
+
"""
|
99
|
+
Restore a single image using DocRes
|
100
|
+
|
101
|
+
Args:
|
102
|
+
image: Image path or numpy array
|
103
|
+
task: Restoration task
|
104
|
+
|
105
|
+
Returns:
|
106
|
+
Tuple of (restored_image, metadata)
|
107
|
+
"""
|
108
|
+
if self._docres is None:
|
109
|
+
raise RuntimeError("DocRes not properly initialized")
|
110
|
+
|
111
|
+
try:
|
112
|
+
return self._docres.restore_image(image, task)
|
113
|
+
except Exception as e:
|
114
|
+
# If it fails due to path issues, try to fix them and retry once
|
115
|
+
if "No such file or directory" in str(e) and "mbd.pkl" in str(e):
|
116
|
+
print("🔧 Attempting to fix DocRes model paths...")
|
117
|
+
self._setup_model_paths()
|
118
|
+
return self._docres.restore_image(image, task)
|
119
|
+
else:
|
120
|
+
raise e
|