scitex 2.4.2__py3-none-any.whl → 2.5.0__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.
- scitex/__version__.py +1 -1
- scitex/browser/__init__.py +53 -0
- scitex/browser/debugging/__init__.py +56 -0
- scitex/browser/debugging/_failure_capture.py +372 -0
- scitex/browser/debugging/_sync_session.py +259 -0
- scitex/browser/debugging/_test_monitor.py +284 -0
- scitex/browser/debugging/_visual_cursor.py +432 -0
- scitex/io/_load.py +5 -0
- scitex/io/_load_modules/_canvas.py +171 -0
- scitex/io/_save.py +8 -0
- scitex/io/_save_modules/_canvas.py +356 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot.py +77 -22
- scitex/plt/docs/FIGURE_ARCHITECTURE.md +257 -0
- scitex/plt/utils/__init__.py +10 -0
- scitex/plt/utils/_collect_figure_metadata.py +14 -12
- scitex/plt/utils/_csv_column_naming.py +237 -0
- scitex/scholar/citation_graph/database.py +9 -2
- scitex/scholar/config/ScholarConfig.py +23 -3
- scitex/scholar/config/default.yaml +55 -0
- scitex/scholar/core/Paper.py +102 -0
- scitex/scholar/core/__init__.py +44 -0
- scitex/scholar/core/journal_normalizer.py +524 -0
- scitex/scholar/core/oa_cache.py +285 -0
- scitex/scholar/core/open_access.py +457 -0
- scitex/scholar/pdf_download/ScholarPDFDownloader.py +137 -0
- scitex/scholar/pdf_download/strategies/__init__.py +6 -0
- scitex/scholar/pdf_download/strategies/open_access_download.py +186 -0
- scitex/scholar/pipelines/ScholarPipelineSearchParallel.py +18 -3
- scitex/scholar/pipelines/ScholarPipelineSearchSingle.py +15 -2
- scitex/session/_decorator.py +13 -1
- scitex/vis/README.md +246 -615
- scitex/vis/__init__.py +138 -78
- scitex/vis/canvas.py +423 -0
- scitex/vis/docs/CANVAS_ARCHITECTURE.md +307 -0
- scitex/vis/editor/__init__.py +1 -1
- scitex/vis/editor/_dearpygui_editor.py +1830 -0
- scitex/vis/editor/_defaults.py +40 -1
- scitex/vis/editor/_edit.py +54 -18
- scitex/vis/editor/_flask_editor.py +37 -0
- scitex/vis/editor/_qt_editor.py +865 -0
- scitex/vis/editor/flask_editor/__init__.py +21 -0
- scitex/vis/editor/flask_editor/bbox.py +216 -0
- scitex/vis/editor/flask_editor/core.py +152 -0
- scitex/vis/editor/flask_editor/plotter.py +130 -0
- scitex/vis/editor/flask_editor/renderer.py +184 -0
- scitex/vis/editor/flask_editor/templates/__init__.py +33 -0
- scitex/vis/editor/flask_editor/templates/html.py +295 -0
- scitex/vis/editor/flask_editor/templates/scripts.py +614 -0
- scitex/vis/editor/flask_editor/templates/styles.py +549 -0
- scitex/vis/editor/flask_editor/utils.py +81 -0
- scitex/vis/io/__init__.py +84 -21
- scitex/vis/io/canvas.py +226 -0
- scitex/vis/io/data.py +204 -0
- scitex/vis/io/directory.py +202 -0
- scitex/vis/io/export.py +460 -0
- scitex/vis/io/panel.py +424 -0
- {scitex-2.4.2.dist-info → scitex-2.5.0.dist-info}/METADATA +9 -2
- {scitex-2.4.2.dist-info → scitex-2.5.0.dist-info}/RECORD +61 -32
- scitex/vis/DJANGO_INTEGRATION.md +0 -677
- scitex/vis/editor/_web_editor.py +0 -1440
- scitex/vis/tmp.txt +0 -239
- {scitex-2.4.2.dist-info → scitex-2.5.0.dist-info}/WHEEL +0 -0
- {scitex-2.4.2.dist-info → scitex-2.5.0.dist-info}/entry_points.txt +0 -0
- {scitex-2.4.2.dist-info → scitex-2.5.0.dist-info}/licenses/LICENSE +0 -0
scitex/vis/DJANGO_INTEGRATION.md
DELETED
|
@@ -1,677 +0,0 @@
|
|
|
1
|
-
# Django Integration Guide for scitex.vis
|
|
2
|
-
|
|
3
|
-
**Integration with `/vis/sigma/` Django Application**
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Overview
|
|
8
|
-
|
|
9
|
-
This guide explains how to integrate `scitex.vis` with the Django-based `/vis/sigma/` application for web-based figure editing and management.
|
|
10
|
-
|
|
11
|
-
## Architecture
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
┌─────────────────────────────────────────────┐
|
|
15
|
-
│ Frontend (React/Vue) │
|
|
16
|
-
│ - Tree view of figure structure │
|
|
17
|
-
│ - Canvas for visual editing │
|
|
18
|
-
│ - Form controls for plot parameters │
|
|
19
|
-
└────────────┬────────────────────────────────┘
|
|
20
|
-
│ HTTP/REST API
|
|
21
|
-
┌────────────▼────────────────────────────────┐
|
|
22
|
-
│ Django Backend (/vis/sigma/) │
|
|
23
|
-
│ - Figure CRUD endpoints │
|
|
24
|
-
│ - Export endpoints │
|
|
25
|
-
│ - Project management │
|
|
26
|
-
└────────────┬────────────────────────────────┘
|
|
27
|
-
│ Python API
|
|
28
|
-
┌────────────▼────────────────────────────────┐
|
|
29
|
-
│ scitex.vis │
|
|
30
|
-
│ - JSON validation │
|
|
31
|
-
│ - Figure rendering │
|
|
32
|
-
│ - Export to PNG/PDF/SVG │
|
|
33
|
-
└─────────────────────────────────────────────┘
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## Required Django API Endpoints
|
|
39
|
-
|
|
40
|
-
### 1. Figure Management
|
|
41
|
-
|
|
42
|
-
#### List Figures in Project
|
|
43
|
-
|
|
44
|
-
```python
|
|
45
|
-
# GET /api/vis/figures/
|
|
46
|
-
def list_figures(request):
|
|
47
|
-
"""List all figures in the project."""
|
|
48
|
-
import scitex as stx
|
|
49
|
-
|
|
50
|
-
project_dir = get_project_dir(request) # Your implementation
|
|
51
|
-
|
|
52
|
-
figure_ids = stx.vis.io.list_figures_in_project(project_dir)
|
|
53
|
-
|
|
54
|
-
return JsonResponse({
|
|
55
|
-
'figures': [
|
|
56
|
-
{
|
|
57
|
-
'id': fig_id,
|
|
58
|
-
'path': f'{project_dir}/scitex/vis/figs/{fig_id}.json'
|
|
59
|
-
}
|
|
60
|
-
for fig_id in figure_ids
|
|
61
|
-
]
|
|
62
|
-
})
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
#### Get Figure JSON
|
|
66
|
-
|
|
67
|
-
```python
|
|
68
|
-
# GET /api/vis/figures/{figure_id}/
|
|
69
|
-
def get_figure(request, figure_id):
|
|
70
|
-
"""Get figure JSON by ID."""
|
|
71
|
-
import scitex as stx
|
|
72
|
-
|
|
73
|
-
project_dir = get_project_dir(request)
|
|
74
|
-
|
|
75
|
-
try:
|
|
76
|
-
fig_json = stx.vis.load_figure_json_from_project(
|
|
77
|
-
project_dir=project_dir,
|
|
78
|
-
figure_id=figure_id
|
|
79
|
-
)
|
|
80
|
-
return JsonResponse(fig_json)
|
|
81
|
-
|
|
82
|
-
except FileNotFoundError:
|
|
83
|
-
return JsonResponse(
|
|
84
|
-
{'error': f'Figure {figure_id} not found'},
|
|
85
|
-
status=404
|
|
86
|
-
)
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
#### Create/Update Figure
|
|
90
|
-
|
|
91
|
-
```python
|
|
92
|
-
# POST /api/vis/figures/{figure_id}/
|
|
93
|
-
def save_figure(request, figure_id):
|
|
94
|
-
"""Save or update figure JSON."""
|
|
95
|
-
import scitex as stx
|
|
96
|
-
from scitex.vis.backend import validate_figure_json
|
|
97
|
-
|
|
98
|
-
project_dir = get_project_dir(request)
|
|
99
|
-
fig_json = json.loads(request.body)
|
|
100
|
-
|
|
101
|
-
# Validate before saving
|
|
102
|
-
try:
|
|
103
|
-
validate_figure_json(fig_json)
|
|
104
|
-
except ValueError as e:
|
|
105
|
-
return JsonResponse(
|
|
106
|
-
{'error': f'Invalid figure JSON: {e}'},
|
|
107
|
-
status=400
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
# Save to project
|
|
111
|
-
path = stx.vis.save_figure_json_to_project(
|
|
112
|
-
project_dir=project_dir,
|
|
113
|
-
figure_id=figure_id,
|
|
114
|
-
fig_json=fig_json
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
return JsonResponse({
|
|
118
|
-
'success': True,
|
|
119
|
-
'figure_id': figure_id,
|
|
120
|
-
'path': str(path)
|
|
121
|
-
})
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
#### Delete Figure
|
|
125
|
-
|
|
126
|
-
```python
|
|
127
|
-
# DELETE /api/vis/figures/{figure_id}/
|
|
128
|
-
def delete_figure(request, figure_id):
|
|
129
|
-
"""Delete figure JSON."""
|
|
130
|
-
project_dir = get_project_dir(request)
|
|
131
|
-
json_path = Path(project_dir) / 'scitex' / 'vis' / 'figs' / f'{figure_id}.json'
|
|
132
|
-
|
|
133
|
-
if json_path.exists():
|
|
134
|
-
json_path.unlink()
|
|
135
|
-
return JsonResponse({'success': True})
|
|
136
|
-
else:
|
|
137
|
-
return JsonResponse(
|
|
138
|
-
{'error': f'Figure {figure_id} not found'},
|
|
139
|
-
status=404
|
|
140
|
-
)
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### 2. Export Endpoints
|
|
144
|
-
|
|
145
|
-
#### Export Figure to Image
|
|
146
|
-
|
|
147
|
-
```python
|
|
148
|
-
# GET /api/vis/figures/{figure_id}/export?format=png&dpi=300
|
|
149
|
-
def export_figure(request, figure_id):
|
|
150
|
-
"""Export figure to image format."""
|
|
151
|
-
import scitex as stx
|
|
152
|
-
|
|
153
|
-
project_dir = get_project_dir(request)
|
|
154
|
-
|
|
155
|
-
# Get parameters
|
|
156
|
-
fmt = request.GET.get('format', 'png') # png, pdf, svg
|
|
157
|
-
dpi = int(request.GET.get('dpi', 300))
|
|
158
|
-
auto_crop = request.GET.get('auto_crop', 'false').lower() == 'true'
|
|
159
|
-
|
|
160
|
-
# Load figure JSON
|
|
161
|
-
try:
|
|
162
|
-
fig_json = stx.vis.load_figure_json_from_project(
|
|
163
|
-
project_dir=project_dir,
|
|
164
|
-
figure_id=figure_id
|
|
165
|
-
)
|
|
166
|
-
except FileNotFoundError:
|
|
167
|
-
return JsonResponse(
|
|
168
|
-
{'error': f'Figure {figure_id} not found'},
|
|
169
|
-
status=404
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
# Export to temporary file
|
|
173
|
-
import tempfile
|
|
174
|
-
with tempfile.NamedTemporaryFile(
|
|
175
|
-
suffix=f'.{fmt}',
|
|
176
|
-
delete=False
|
|
177
|
-
) as tmp:
|
|
178
|
-
tmp_path = tmp.name
|
|
179
|
-
|
|
180
|
-
try:
|
|
181
|
-
stx.vis.export_figure(
|
|
182
|
-
fig_json=fig_json,
|
|
183
|
-
output_path=tmp_path,
|
|
184
|
-
fmt=fmt,
|
|
185
|
-
dpi=dpi,
|
|
186
|
-
auto_crop=auto_crop
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
# Return file
|
|
190
|
-
with open(tmp_path, 'rb') as f:
|
|
191
|
-
response = HttpResponse(
|
|
192
|
-
f.read(),
|
|
193
|
-
content_type=f'image/{fmt}' if fmt in ['png', 'svg'] else 'application/pdf'
|
|
194
|
-
)
|
|
195
|
-
response['Content-Disposition'] = f'attachment; filename="{figure_id}.{fmt}"'
|
|
196
|
-
return response
|
|
197
|
-
|
|
198
|
-
finally:
|
|
199
|
-
# Clean up temp file
|
|
200
|
-
if os.path.exists(tmp_path):
|
|
201
|
-
os.unlink(tmp_path)
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
#### Export Multiple Formats
|
|
205
|
-
|
|
206
|
-
```python
|
|
207
|
-
# POST /api/vis/figures/{figure_id}/export-multi/
|
|
208
|
-
def export_multiple_formats(request, figure_id):
|
|
209
|
-
"""Export figure to multiple formats simultaneously."""
|
|
210
|
-
import scitex as stx
|
|
211
|
-
|
|
212
|
-
project_dir = get_project_dir(request)
|
|
213
|
-
formats = request.POST.getlist('formats', ['png', 'pdf', 'svg'])
|
|
214
|
-
|
|
215
|
-
# Load figure
|
|
216
|
-
fig_json = stx.vis.load_figure_json_from_project(
|
|
217
|
-
project_dir=project_dir,
|
|
218
|
-
figure_id=figure_id
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
# Export to project export directory
|
|
222
|
-
export_dir = Path(project_dir) / 'scitex' / 'vis' / 'export'
|
|
223
|
-
|
|
224
|
-
paths = stx.vis.backend.export_multiple_formats(
|
|
225
|
-
fig_json=fig_json,
|
|
226
|
-
output_dir=export_dir,
|
|
227
|
-
base_name=figure_id,
|
|
228
|
-
formats=formats,
|
|
229
|
-
dpi=300,
|
|
230
|
-
auto_crop=True
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
return JsonResponse({
|
|
234
|
-
'success': True,
|
|
235
|
-
'files': {fmt: str(path) for fmt, path in paths.items()}
|
|
236
|
-
})
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### 3. Template Endpoints
|
|
240
|
-
|
|
241
|
-
#### List Available Templates
|
|
242
|
-
|
|
243
|
-
```python
|
|
244
|
-
# GET /api/vis/templates/
|
|
245
|
-
def list_templates(request):
|
|
246
|
-
"""List available figure templates."""
|
|
247
|
-
import scitex as stx
|
|
248
|
-
|
|
249
|
-
templates = stx.vis.list_templates()
|
|
250
|
-
|
|
251
|
-
template_info = []
|
|
252
|
-
for name in templates:
|
|
253
|
-
template = stx.vis.get_template(name)
|
|
254
|
-
template_info.append({
|
|
255
|
-
'name': name,
|
|
256
|
-
'width_mm': template['width_mm'],
|
|
257
|
-
'height_mm': template['height_mm'],
|
|
258
|
-
'description': template.get('metadata', {}).get('template', name)
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
return JsonResponse({'templates': template_info})
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
#### Get Template
|
|
265
|
-
|
|
266
|
-
```python
|
|
267
|
-
# GET /api/vis/templates/{template_name}/
|
|
268
|
-
def get_template(request, template_name):
|
|
269
|
-
"""Get a specific template."""
|
|
270
|
-
import scitex as stx
|
|
271
|
-
|
|
272
|
-
try:
|
|
273
|
-
# Get optional parameters
|
|
274
|
-
height_mm = request.GET.get('height_mm')
|
|
275
|
-
nrows = request.GET.get('nrows')
|
|
276
|
-
ncols = request.GET.get('ncols')
|
|
277
|
-
|
|
278
|
-
kwargs = {}
|
|
279
|
-
if height_mm:
|
|
280
|
-
kwargs['height_mm'] = float(height_mm)
|
|
281
|
-
if nrows:
|
|
282
|
-
kwargs['nrows'] = int(nrows)
|
|
283
|
-
if ncols:
|
|
284
|
-
kwargs['ncols'] = int(ncols)
|
|
285
|
-
|
|
286
|
-
template = stx.vis.get_template(template_name, **kwargs)
|
|
287
|
-
return JsonResponse(template)
|
|
288
|
-
|
|
289
|
-
except ValueError as e:
|
|
290
|
-
return JsonResponse(
|
|
291
|
-
{'error': f'Unknown template: {template_name}'},
|
|
292
|
-
status=404
|
|
293
|
-
)
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### 4. Validation Endpoint
|
|
297
|
-
|
|
298
|
-
```python
|
|
299
|
-
# POST /api/vis/validate/
|
|
300
|
-
def validate_figure_json(request):
|
|
301
|
-
"""Validate figure JSON without saving."""
|
|
302
|
-
from scitex.vis.backend import validate_figure_json
|
|
303
|
-
|
|
304
|
-
fig_json = json.loads(request.body)
|
|
305
|
-
|
|
306
|
-
try:
|
|
307
|
-
validate_figure_json(fig_json)
|
|
308
|
-
return JsonResponse({
|
|
309
|
-
'valid': True,
|
|
310
|
-
'message': 'Figure JSON is valid'
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
except ValueError as e:
|
|
314
|
-
return JsonResponse({
|
|
315
|
-
'valid': False,
|
|
316
|
-
'error': str(e)
|
|
317
|
-
}, status=400)
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
---
|
|
321
|
-
|
|
322
|
-
## URL Configuration
|
|
323
|
-
|
|
324
|
-
```python
|
|
325
|
-
# urls.py
|
|
326
|
-
from django.urls import path
|
|
327
|
-
from . import views
|
|
328
|
-
|
|
329
|
-
urlpatterns = [
|
|
330
|
-
# Figure management
|
|
331
|
-
path('api/vis/figures/', views.list_figures, name='list_figures'),
|
|
332
|
-
path('api/vis/figures/<str:figure_id>/', views.get_figure, name='get_figure'),
|
|
333
|
-
path('api/vis/figures/<str:figure_id>/', views.save_figure, name='save_figure'),
|
|
334
|
-
path('api/vis/figures/<str:figure_id>/', views.delete_figure, name='delete_figure'),
|
|
335
|
-
|
|
336
|
-
# Export
|
|
337
|
-
path('api/vis/figures/<str:figure_id>/export/', views.export_figure, name='export_figure'),
|
|
338
|
-
path('api/vis/figures/<str:figure_id>/export-multi/', views.export_multiple_formats, name='export_multiple'),
|
|
339
|
-
|
|
340
|
-
# Templates
|
|
341
|
-
path('api/vis/templates/', views.list_templates, name='list_templates'),
|
|
342
|
-
path('api/vis/templates/<str:template_name>/', views.get_template, name='get_template'),
|
|
343
|
-
|
|
344
|
-
# Validation
|
|
345
|
-
path('api/vis/validate/', views.validate_figure_json, name='validate'),
|
|
346
|
-
]
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
---
|
|
350
|
-
|
|
351
|
-
## Frontend Integration
|
|
352
|
-
|
|
353
|
-
### Loading CSV Data for Plots
|
|
354
|
-
|
|
355
|
-
When the frontend needs to create a plot, it should:
|
|
356
|
-
|
|
357
|
-
1. **Upload CSV** to Django backend
|
|
358
|
-
2. **Parse CSV** to extract x, y data
|
|
359
|
-
3. **Convert to JSON arrays** for plot data
|
|
360
|
-
4. **Send to scitex.vis** as part of figure JSON
|
|
361
|
-
|
|
362
|
-
```python
|
|
363
|
-
# POST /api/vis/data/upload/
|
|
364
|
-
def upload_plot_data(request):
|
|
365
|
-
"""Upload CSV and convert to plot data."""
|
|
366
|
-
import pandas as pd
|
|
367
|
-
import io
|
|
368
|
-
|
|
369
|
-
csv_file = request.FILES['file']
|
|
370
|
-
x_column = request.POST.get('x_column', 0)
|
|
371
|
-
y_column = request.POST.get('y_column', 1)
|
|
372
|
-
|
|
373
|
-
# Read CSV
|
|
374
|
-
df = pd.read_csv(io.BytesIO(csv_file.read()))
|
|
375
|
-
|
|
376
|
-
# Extract columns
|
|
377
|
-
x_data = df.iloc[:, x_column].tolist()
|
|
378
|
-
y_data = df.iloc[:, y_column].tolist()
|
|
379
|
-
|
|
380
|
-
return JsonResponse({
|
|
381
|
-
'data': {
|
|
382
|
-
'x': x_data,
|
|
383
|
-
'y': y_data
|
|
384
|
-
},
|
|
385
|
-
'columns': df.columns.tolist()
|
|
386
|
-
})
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
### Frontend Workflow
|
|
390
|
-
|
|
391
|
-
1. **Select Template**
|
|
392
|
-
```javascript
|
|
393
|
-
const template = await fetch('/api/vis/templates/nature_single/').then(r => r.json());
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
2. **Upload Data**
|
|
397
|
-
```javascript
|
|
398
|
-
const formData = new FormData();
|
|
399
|
-
formData.append('file', csvFile);
|
|
400
|
-
formData.append('x_column', 0);
|
|
401
|
-
formData.append('y_column', 1);
|
|
402
|
-
|
|
403
|
-
const plotData = await fetch('/api/vis/data/upload/', {
|
|
404
|
-
method: 'POST',
|
|
405
|
-
body: formData
|
|
406
|
-
}).then(r => r.json());
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
3. **Build Figure JSON**
|
|
410
|
-
```javascript
|
|
411
|
-
const figJson = {
|
|
412
|
-
...template,
|
|
413
|
-
axes: [{
|
|
414
|
-
row: 0,
|
|
415
|
-
col: 0,
|
|
416
|
-
xlabel: "Time (s)",
|
|
417
|
-
ylabel: "Amplitude",
|
|
418
|
-
plots: [{
|
|
419
|
-
plot_type: "line",
|
|
420
|
-
data: plotData.data,
|
|
421
|
-
color: "blue",
|
|
422
|
-
linewidth: 2
|
|
423
|
-
}]
|
|
424
|
-
}]
|
|
425
|
-
};
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
4. **Save Figure**
|
|
429
|
-
```javascript
|
|
430
|
-
await fetch('/api/vis/figures/fig-001/', {
|
|
431
|
-
method: 'POST',
|
|
432
|
-
headers: {'Content-Type': 'application/json'},
|
|
433
|
-
body: JSON.stringify(figJson)
|
|
434
|
-
});
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
5. **Export**
|
|
438
|
-
```javascript
|
|
439
|
-
const imageBlob = await fetch(
|
|
440
|
-
'/api/vis/figures/fig-001/export?format=png&dpi=300'
|
|
441
|
-
).then(r => r.blob());
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
---
|
|
445
|
-
|
|
446
|
-
## Project Directory Structure
|
|
447
|
-
|
|
448
|
-
```
|
|
449
|
-
project/
|
|
450
|
-
├── scitex/
|
|
451
|
-
│ └── vis/
|
|
452
|
-
│ ├── figs/ # Figure JSON specifications
|
|
453
|
-
│ │ ├── fig-001.json
|
|
454
|
-
│ │ ├── fig-002.json
|
|
455
|
-
│ │ └── ...
|
|
456
|
-
│ ├── export/ # Exported images
|
|
457
|
-
│ │ ├── fig-001.png
|
|
458
|
-
│ │ ├── fig-001.pdf
|
|
459
|
-
│ │ └── ...
|
|
460
|
-
│ └── data/ # Optional: CSV data files
|
|
461
|
-
│ ├── dataset-01.csv
|
|
462
|
-
│ └── ...
|
|
463
|
-
└── ...
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
---
|
|
467
|
-
|
|
468
|
-
## Security Considerations
|
|
469
|
-
|
|
470
|
-
### 1. Input Validation
|
|
471
|
-
|
|
472
|
-
Always validate figure JSON before processing:
|
|
473
|
-
|
|
474
|
-
```python
|
|
475
|
-
from scitex.vis.backend import validate_figure_json
|
|
476
|
-
|
|
477
|
-
try:
|
|
478
|
-
validate_figure_json(fig_json)
|
|
479
|
-
except ValueError as e:
|
|
480
|
-
return JsonResponse({'error': str(e)}, status=400)
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
### 2. File Path Sanitization
|
|
484
|
-
|
|
485
|
-
Prevent directory traversal attacks:
|
|
486
|
-
|
|
487
|
-
```python
|
|
488
|
-
import os
|
|
489
|
-
from pathlib import Path
|
|
490
|
-
|
|
491
|
-
def get_safe_figure_path(project_dir, figure_id):
|
|
492
|
-
"""Get sanitized figure path."""
|
|
493
|
-
# Remove any path components
|
|
494
|
-
safe_id = Path(figure_id).name
|
|
495
|
-
|
|
496
|
-
# Ensure .json extension
|
|
497
|
-
if not safe_id.endswith('.json'):
|
|
498
|
-
safe_id = f'{safe_id}.json'
|
|
499
|
-
|
|
500
|
-
path = Path(project_dir) / 'scitex' / 'vis' / 'figs' / safe_id
|
|
501
|
-
|
|
502
|
-
# Ensure path is within project directory
|
|
503
|
-
if not str(path.resolve()).startswith(str(Path(project_dir).resolve())):
|
|
504
|
-
raise ValueError('Invalid figure ID')
|
|
505
|
-
|
|
506
|
-
return path
|
|
507
|
-
```
|
|
508
|
-
|
|
509
|
-
### 3. Rate Limiting
|
|
510
|
-
|
|
511
|
-
Apply rate limiting to export endpoints:
|
|
512
|
-
|
|
513
|
-
```python
|
|
514
|
-
from django.views.decorators.cache import cache_page
|
|
515
|
-
from django.views.decorators.ratelimit import ratelimit
|
|
516
|
-
|
|
517
|
-
@ratelimit(key='user', rate='10/m', method='GET')
|
|
518
|
-
def export_figure(request, figure_id):
|
|
519
|
-
# ... export logic
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
---
|
|
523
|
-
|
|
524
|
-
## Performance Optimization
|
|
525
|
-
|
|
526
|
-
### 1. Caching
|
|
527
|
-
|
|
528
|
-
Cache rendered figures:
|
|
529
|
-
|
|
530
|
-
```python
|
|
531
|
-
from django.core.cache import cache
|
|
532
|
-
|
|
533
|
-
def export_figure(request, figure_id):
|
|
534
|
-
cache_key = f'figure_{figure_id}_{fmt}_{dpi}'
|
|
535
|
-
|
|
536
|
-
cached = cache.get(cache_key)
|
|
537
|
-
if cached:
|
|
538
|
-
return cached
|
|
539
|
-
|
|
540
|
-
# Render and cache
|
|
541
|
-
response = render_and_export(fig_json, fmt, dpi)
|
|
542
|
-
cache.set(cache_key, response, timeout=3600) # 1 hour
|
|
543
|
-
return response
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
### 2. Background Tasks
|
|
547
|
-
|
|
548
|
-
Use Celery for slow export operations:
|
|
549
|
-
|
|
550
|
-
```python
|
|
551
|
-
from celery import shared_task
|
|
552
|
-
|
|
553
|
-
@shared_task
|
|
554
|
-
def export_figure_task(project_dir, figure_id, fmt, dpi):
|
|
555
|
-
"""Background task for figure export."""
|
|
556
|
-
import scitex as stx
|
|
557
|
-
|
|
558
|
-
fig_json = stx.vis.load_figure_json_from_project(project_dir, figure_id)
|
|
559
|
-
output_path = Path(project_dir) / 'scitex' / 'vis' / 'export' / f'{figure_id}.{fmt}'
|
|
560
|
-
|
|
561
|
-
stx.vis.export_figure(fig_json, output_path, fmt=fmt, dpi=dpi)
|
|
562
|
-
|
|
563
|
-
return str(output_path)
|
|
564
|
-
|
|
565
|
-
# In view:
|
|
566
|
-
def export_figure(request, figure_id):
|
|
567
|
-
task = export_figure_task.delay(project_dir, figure_id, fmt, dpi)
|
|
568
|
-
return JsonResponse({'task_id': task.id})
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
---
|
|
572
|
-
|
|
573
|
-
## Testing
|
|
574
|
-
|
|
575
|
-
### Unit Tests
|
|
576
|
-
|
|
577
|
-
```python
|
|
578
|
-
from django.test import TestCase
|
|
579
|
-
import scitex as stx
|
|
580
|
-
|
|
581
|
-
class VisFigureTests(TestCase):
|
|
582
|
-
def test_save_and_load_figure(self):
|
|
583
|
-
"""Test figure save/load cycle."""
|
|
584
|
-
fig_json = stx.vis.get_template('square')
|
|
585
|
-
|
|
586
|
-
# Save
|
|
587
|
-
path = stx.vis.save_figure_json_to_project(
|
|
588
|
-
'/tmp/test_project',
|
|
589
|
-
'test-fig',
|
|
590
|
-
fig_json
|
|
591
|
-
)
|
|
592
|
-
|
|
593
|
-
# Load
|
|
594
|
-
loaded = stx.vis.load_figure_json_from_project(
|
|
595
|
-
'/tmp/test_project',
|
|
596
|
-
'test-fig'
|
|
597
|
-
)
|
|
598
|
-
|
|
599
|
-
self.assertEqual(fig_json['width_mm'], loaded['width_mm'])
|
|
600
|
-
|
|
601
|
-
def test_export_figure(self):
|
|
602
|
-
"""Test figure export."""
|
|
603
|
-
fig_json = stx.vis.get_template('square')
|
|
604
|
-
fig_json['axes'] = [{
|
|
605
|
-
'plots': [{
|
|
606
|
-
'plot_type': 'line',
|
|
607
|
-
'data': {'x': [0, 1, 2], 'y': [0, 1, 4]}
|
|
608
|
-
}]
|
|
609
|
-
}]
|
|
610
|
-
|
|
611
|
-
output = '/tmp/test.png'
|
|
612
|
-
stx.vis.export_figure(fig_json, output, dpi=150)
|
|
613
|
-
|
|
614
|
-
self.assertTrue(Path(output).exists())
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
### Integration Tests
|
|
618
|
-
|
|
619
|
-
```python
|
|
620
|
-
class VisAPITests(TestCase):
|
|
621
|
-
def test_list_figures(self):
|
|
622
|
-
"""Test figure listing endpoint."""
|
|
623
|
-
response = self.client.get('/api/vis/figures/')
|
|
624
|
-
self.assertEqual(response.status_code, 200)
|
|
625
|
-
self.assertIn('figures', response.json())
|
|
626
|
-
|
|
627
|
-
def test_save_figure(self):
|
|
628
|
-
"""Test figure save endpoint."""
|
|
629
|
-
fig_json = stx.vis.get_template('square')
|
|
630
|
-
|
|
631
|
-
response = self.client.post(
|
|
632
|
-
'/api/vis/figures/test-fig/',
|
|
633
|
-
data=json.dumps(fig_json),
|
|
634
|
-
content_type='application/json'
|
|
635
|
-
)
|
|
636
|
-
|
|
637
|
-
self.assertEqual(response.status_code, 200)
|
|
638
|
-
self.assertTrue(response.json()['success'])
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
---
|
|
642
|
-
|
|
643
|
-
## Troubleshooting
|
|
644
|
-
|
|
645
|
-
### Common Issues
|
|
646
|
-
|
|
647
|
-
1. **Figure not rendering**
|
|
648
|
-
- Check if scitex.plt is properly installed
|
|
649
|
-
- Verify matplotlib backend is available
|
|
650
|
-
- Check for missing data in plot configurations
|
|
651
|
-
|
|
652
|
-
2. **Validation errors**
|
|
653
|
-
- Use `/api/vis/validate/` endpoint to check JSON
|
|
654
|
-
- Review error messages for specific field issues
|
|
655
|
-
- Ensure all required fields are present
|
|
656
|
-
|
|
657
|
-
3. **Export failures**
|
|
658
|
-
- Check disk space for export directory
|
|
659
|
-
- Verify write permissions
|
|
660
|
-
- Check matplotlib/scitex.plt configuration
|
|
661
|
-
|
|
662
|
-
---
|
|
663
|
-
|
|
664
|
-
## Next Steps
|
|
665
|
-
|
|
666
|
-
1. **Implement Django views** using the examples above
|
|
667
|
-
2. **Create URL patterns** for all endpoints
|
|
668
|
-
3. **Build frontend components** for:
|
|
669
|
-
- Figure tree viewer
|
|
670
|
-
- Visual canvas editor
|
|
671
|
-
- Form controls for plot parameters
|
|
672
|
-
4. **Test integration** with sample figures
|
|
673
|
-
5. **Deploy** to production environment
|
|
674
|
-
|
|
675
|
-
---
|
|
676
|
-
|
|
677
|
-
**Integration complete! Ready to connect `/vis/sigma/` Django app with `scitex.vis`** 🚀
|