batoolset-gui 0.2.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.
Files changed (74) hide show
  1. batoolset/GUI/__init__.py +0 -0
  2. batoolset/GUI/customdialog.py +136 -0
  3. batoolset/GUI/exception_handler.py +74 -0
  4. batoolset/GUI/list_gui.py +514 -0
  5. batoolset/GUI/multi_image_display.py +1038 -0
  6. batoolset/GUI/open.py +325 -0
  7. batoolset/GUI/overlay_hints.py +45 -0
  8. batoolset/GUI/paint2.py +1369 -0
  9. batoolset/GUI/scrollablepaint.py +1165 -0
  10. batoolset/dialogs/__init__.py +0 -0
  11. batoolset/dialogs/opensave.py +228 -0
  12. batoolset/drawings/__init__.py +0 -0
  13. batoolset/drawings/math_text.py +294 -0
  14. batoolset/drawings/pdf_svg_renderer.py +376 -0
  15. batoolset/drawings/shapes/Position.py +183 -0
  16. batoolset/drawings/shapes/__init__.py +0 -0
  17. batoolset/drawings/shapes/automated_alignment_drawing_refactored.py +346 -0
  18. batoolset/drawings/shapes/circle2d.py +99 -0
  19. batoolset/drawings/shapes/compute_optimal_size_all_merged.py +328 -0
  20. batoolset/drawings/shapes/ellipse2d.py +336 -0
  21. batoolset/drawings/shapes/freehand2d.py +235 -0
  22. batoolset/drawings/shapes/group.py +1985 -0
  23. batoolset/drawings/shapes/image2d.py +3157 -0
  24. batoolset/drawings/shapes/line2d.py +579 -0
  25. batoolset/drawings/shapes/point2d.py +257 -0
  26. batoolset/drawings/shapes/polygon2d.py +357 -0
  27. batoolset/drawings/shapes/polyline2d.py +235 -0
  28. batoolset/drawings/shapes/rect2d.py +388 -0
  29. batoolset/drawings/shapes/rectangle2d.py +601 -0
  30. batoolset/drawings/shapes/scalebar.py +310 -0
  31. batoolset/drawings/shapes/serializablefont.py +133 -0
  32. batoolset/drawings/shapes/serializableguiparams.py +14 -0
  33. batoolset/drawings/shapes/square2d.py +102 -0
  34. batoolset/drawings/shapes/test_publication_style.py +220 -0
  35. batoolset/drawings/shapes/textorientation.py +45 -0
  36. batoolset/drawings/shapes/tools.py +467 -0
  37. batoolset/drawings/shapes/transform2D.py +350 -0
  38. batoolset/drawings/shapes/txt2d.py +615 -0
  39. batoolset/drawings/svg_clip_renderer.py +247 -0
  40. batoolset/drawings/svg_embed.py +165 -0
  41. batoolset/drawings/svg_text_renderer.py +183 -0
  42. batoolset/drawings/widgets/__init__.py +0 -0
  43. batoolset/drawings/widgets/col.py +343 -0
  44. batoolset/drawings/widgets/image_group.py +248 -0
  45. batoolset/drawings/widgets/image_organizer.py +96 -0
  46. batoolset/drawings/widgets/paint.py +184 -0
  47. batoolset/drawings/widgets/vectorial.py +256 -0
  48. batoolset/figure/__init__.py +0 -0
  49. batoolset/figure/alignment.py +822 -0
  50. batoolset/figure/column.py +1117 -0
  51. batoolset/figure/fig_tools.py +285 -0
  52. batoolset/figure/minimal_debugger_scale_etc.py +139 -0
  53. batoolset/figure/panel.py +697 -0
  54. batoolset/figure/row.py +852 -0
  55. batoolset/figure/user_selection.py +287 -0
  56. batoolset/icons/__init__.py +0 -0
  57. batoolset/icons/tools.py +248 -0
  58. batoolset/luts/__init__.py +0 -0
  59. batoolset/luts/lut_minimal_test.py +1308 -0
  60. batoolset/luts/lutcreatorfromformula.py +209 -0
  61. batoolset/luts/luteditor.py +243 -0
  62. batoolset/pyqts/__init__.py +0 -0
  63. batoolset/pyqts/pleasewaitdialog.py +14 -0
  64. batoolset/pyqts/tools.py +760 -0
  65. batoolset/serializations/__init__.py +0 -0
  66. batoolset/serializations/tools.py +1338 -0
  67. batoolset/theme.py +647 -0
  68. batoolset/uis/__init__.py +0 -0
  69. batoolset/uis/blinker.py +56 -0
  70. batoolset_gui-0.2.0.dist-info/METADATA +54 -0
  71. batoolset_gui-0.2.0.dist-info/RECORD +74 -0
  72. batoolset_gui-0.2.0.dist-info/WHEEL +5 -0
  73. batoolset_gui-0.2.0.dist-info/licenses/LICENSE +24 -0
  74. batoolset_gui-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1338 @@
1
+ from batoolset.settings.global_settings import set_UI # set the UI to qtpy
2
+ set_UI()
3
+ from builtins import int
4
+ from batoolset.files.tools import read_string_from_file
5
+ import xml.etree.ElementTree as ET
6
+ import xml.dom.minidom
7
+ from batoolset.drawings.shapes.txt2d import TAText2D
8
+ from batoolset.drawings.shapes.Position import Position
9
+ import ast
10
+ # from batoolset.serializations.tools import deserialize_to_dict
11
+
12
+
13
+ # Calls allowed inside a serialized value: numpy-2 scalar reprs such as
14
+ # np.float64(1.5) and plain type conversions. Nothing else can be called.
15
+ _SAFE_CALLS = {
16
+ 'float': float, 'int': int, 'bool': bool, 'str': str,
17
+ 'float16': float, 'float32': float, 'float64': float, 'longdouble': float,
18
+ 'int8': int, 'int16': int, 'int32': int, 'int64': int,
19
+ 'uint8': int, 'uint16': int, 'uint32': int, 'uint64': int,
20
+ 'bool_': bool, 'str_': str,
21
+ }
22
+ _SAFE_NAMES = {'nan': float('nan'), 'inf': float('inf'), 'None': None,
23
+ 'True': True, 'False': False}
24
+
25
+
26
+ def safe_literal_eval(text):
27
+ """Parse a serialized Python literal WITHOUT executing code.
28
+
29
+ Replaces the eval() calls previously used when reading .pyf files: eval ran
30
+ any expression stored in the file (e.g. ``[__import__('os').system(...)]``)
31
+ as soon as a figure was opened. Accepts literals (lists, tuples, dicts, sets,
32
+ numbers, strings), negative numbers, nan/inf and numpy scalar reprs like
33
+ ``np.float64(1.5)``; raises ValueError on anything else."""
34
+ tree = ast.parse(text.strip(), mode='eval')
35
+
36
+ def _ev(node):
37
+ if isinstance(node, ast.Expression):
38
+ return _ev(node.body)
39
+ if isinstance(node, ast.Constant):
40
+ return node.value
41
+ if isinstance(node, ast.List):
42
+ return [_ev(e) for e in node.elts]
43
+ if isinstance(node, ast.Tuple):
44
+ return tuple(_ev(e) for e in node.elts)
45
+ if isinstance(node, ast.Set):
46
+ return {_ev(e) for e in node.elts}
47
+ if isinstance(node, ast.Dict):
48
+ return {_ev(k): _ev(v) for k, v in zip(node.keys, node.values)}
49
+ if isinstance(node, ast.UnaryOp) and isinstance(node.op, (ast.USub, ast.UAdd)):
50
+ v = _ev(node.operand)
51
+ if not isinstance(v, (int, float)):
52
+ raise ValueError('unary operator on a non-number')
53
+ return -v if isinstance(node.op, ast.USub) else +v
54
+ if isinstance(node, ast.Name) and node.id in _SAFE_NAMES:
55
+ return _SAFE_NAMES[node.id]
56
+ if isinstance(node, ast.Call) and not node.keywords and len(node.args) <= 1:
57
+ fn = node.func
58
+ name = None
59
+ if isinstance(fn, ast.Name):
60
+ name = fn.id
61
+ elif isinstance(fn, ast.Attribute) and isinstance(fn.value, ast.Name) \
62
+ and fn.value.id in ('np', 'numpy'):
63
+ name = fn.attr
64
+ if name in _SAFE_CALLS:
65
+ args = [_ev(a) for a in node.args]
66
+ return _SAFE_CALLS[name](*args)
67
+ raise ValueError('unsafe or unsupported expression in .pyf value: '
68
+ + ast.dump(node)[:80])
69
+
70
+ return _ev(tree)
71
+
72
+
73
+ _OBJECT_REPR_RE = __import__('re').compile(r'^[A-Za-z_][A-Za-z0-9_]*-0x[0-9a-fA-F]+$')
74
+
75
+
76
+ def _is_unserializable_object(value):
77
+ """True for plain attribute objects that would be written as their repr
78
+ ("Class-0x…", i.e. a memory address) — they carry no data and cannot be
79
+ restored. Serializable kinds (strings, numbers, containers, text, positions)
80
+ are never skipped. (Attribute objects are not recursed into here — even ones
81
+ with to_dict() are written via str() — so the repr is all that would land.)"""
82
+ if value is None or isinstance(value, (str, int, float, bool, list, tuple, dict,
83
+ TAText2D, Position)):
84
+ return False
85
+ try:
86
+ return bool(_OBJECT_REPR_RE.match(str(value)))
87
+ except Exception:
88
+ return False
89
+
90
+
91
+ # see key=='coords_as_list' and not key=='range': # excetptions that should not be written as texts even though they are texts for exceptions
92
+ def object_to_xml(obj, element=None, keys_to_ignore=['img','renderer','doc','qimage'], is_list=False):
93
+ obj_dict = None
94
+ if element is None:
95
+ # if is_list:
96
+ # element = ET.Element("list")
97
+ # else:
98
+ element = ET.Element(type(obj).__name__)
99
+
100
+ if hasattr(obj, 'to_dict'):
101
+ obj_dict = obj.to_dict()
102
+ elif isinstance(obj, (list, tuple)):
103
+ if is_list:
104
+ for item in obj:
105
+ child = ET.SubElement(element, type(item).__name__)
106
+ object_to_xml(item, child)
107
+ else:
108
+ obj_dict = [object_to_xml(item) for item in obj]
109
+ elif isinstance(obj, dict):
110
+ obj_dict = {k: object_to_xml(v) for k, v in obj.items()}
111
+ elif isinstance(obj, (str, int, float, bool)):
112
+ obj_dict = obj
113
+ else:
114
+ obj_dict = obj.__dict__
115
+ if isinstance(obj_dict, dict):
116
+ for key, value in obj_dict.items():
117
+ if keys_to_ignore and key in keys_to_ignore:
118
+ continue
119
+ if _is_unserializable_object(value):
120
+ # e.g. ScaleBar.bar (a helper Rectangle2D): str() gives
121
+ # "Rectangle2D-0x2731…", a memory address that is ignored on load
122
+ # and made every save differ (non-reproducible files)
123
+ continue
124
+
125
+ child = ET.SubElement(element, key)
126
+
127
+ if isinstance(value, TAText2D):
128
+ child.text = value.getHtmlText()
129
+ elif isinstance(value, Position): # force serialization of Position as a string because it is much better
130
+ child.text = str(value)
131
+ elif isinstance(value, (list, tuple)):
132
+ for item in value:
133
+ grandchild = ET.SubElement(child, type(item).__name__)
134
+ object_to_xml(item, grandchild) # --> this makes an infinite loop but just with the Image2D with annotation = []
135
+ # elif hasattr(value, '__dict__'):
136
+ # object_to_xml(value, child)
137
+ else:
138
+ if isinstance(value, str) and not key=='coords_as_list' and not key=='range' and not key=='LUTs': # excetptions that should not be written as texts even though they are texts
139
+ child.text = f'""{str(value)}""'
140
+ else:
141
+ child.text = str(value)
142
+ else:
143
+ if obj_dict:
144
+ element.text = str(obj_dict)
145
+ else:
146
+ element.text = None
147
+
148
+ rough_string = ET.tostring(element, encoding='unicode', method='xml', xml_declaration=False)
149
+ reparsed = xml.dom.minidom.parseString(rough_string)
150
+ return reparsed.toprettyxml(indent=" ")
151
+
152
+ def _report_failed_object(obj_type):
153
+ """Say WHY an object could not be rebuilt (its parameters alone hid a
154
+ layout ZeroDivisionError that dropped a whole group — most of a figure).
155
+ Never raises, also on a console that cannot show the traceback's text."""
156
+ import traceback
157
+ msg = f'ERROR deserialization: {obj_type} could not be rebuilt:\n{traceback.format_exc()}'
158
+ try:
159
+ print(msg)
160
+ except UnicodeEncodeError:
161
+ print(msg.encode('ascii', 'backslashreplace').decode('ascii'))
162
+
163
+
164
+ def create_objects_from_dict(objects_dict):
165
+ annotations = []
166
+
167
+ if isinstance(objects_dict, OrderedChildren):
168
+ # mixed tags (groups and images): rebuild them in document order
169
+ objects_dict = [{tag: value} for tag, value in objects_dict.order]
170
+
171
+ if isinstance(objects_dict, dict):
172
+
173
+ for obj_type, parameters in objects_dict.items(): # big pb it does not save and store the right index of the object in the list -−> see how I can do that and preserve it to sort them back
174
+ try:
175
+ if isinstance(parameters, list): # if there are many objects then one deserializes then all
176
+ for parameter in parameters:
177
+ annotations.append(create_object(obj_type, parameter))
178
+ else:
179
+ annotations.append(create_object(obj_type, parameters))
180
+ except Exception:
181
+ _report_failed_object(obj_type)
182
+ else:
183
+ # dirty hack to recover annotations in the right order
184
+ for dct in objects_dict: # big pb it does not save and store the right index of the object in the list -−> see how I can do that and preserve it to sort them back
185
+ obj_type, parameters = list(dct.items())[0]
186
+ try:
187
+ if isinstance(parameters, list): # if there are many objects then one deserializes then all
188
+ for parameter in parameters:
189
+ annotations.append(create_object(obj_type, parameter))
190
+ else:
191
+ annotations.append(create_object(obj_type, parameters))
192
+ except Exception:
193
+ _report_failed_object(obj_type)
194
+ return annotations
195
+
196
+ def clone_object(objects, auto_unpack_if_single=True, overrides=None): # overrides offers the possibility to mutate one of the parameters of the clone!
197
+ from batoolset.drawings.shapes.image2d import Image2D
198
+ clones = []
199
+ if isinstance(objects, list) and not objects:
200
+ return clones
201
+ if not isinstance(objects, (Image2D, TAText2D)) and not objects:
202
+ return clones
203
+ # if objects is None:
204
+ # return clones
205
+ # if not objects: # why is this shit considered to be empty
206
+ # print('Error nothing to clone', objects, not objects)
207
+ # return clones
208
+ if not isinstance(objects,list):
209
+ objects=[objects]
210
+ for object in objects:
211
+ xml_string = object_to_xml(object)
212
+ # get the class of the instance
213
+ instance_class = type(object)
214
+ # get the name of the class
215
+ class_name = instance_class.__name__
216
+ # print('in there', class_name, 'oubsi')
217
+ original_dict = deserialize_to_dict(xml_string)
218
+ if isinstance(overrides, dict):
219
+ original_dict.update(overrides)
220
+ clones.append(create_object(class_name, original_dict))
221
+ if auto_unpack_if_single and len(clones) == 1:
222
+ return clones[0]
223
+ return clones
224
+
225
+ def create_object(object_type,properties):
226
+ from batoolset.drawings.shapes.circle2d import Circle2D
227
+ from batoolset.drawings.shapes.image2d import Image2D
228
+ from batoolset.drawings.shapes.ellipse2d import Ellipse2D
229
+ from batoolset.drawings.shapes.freehand2d import Freehand2D
230
+ from batoolset.drawings.shapes.rectangle2d import Rectangle2D
231
+ from batoolset.drawings.shapes.line2d import Line2D
232
+ from batoolset.drawings.shapes.point2d import Point2D
233
+ from batoolset.drawings.shapes.polygon2d import Polygon2D
234
+ from batoolset.drawings.shapes.polyline2d import PolyLine2D
235
+ from batoolset.drawings.shapes.scalebar import ScaleBar
236
+ from batoolset.drawings.shapes.square2d import Square2D
237
+ from batoolset.drawings.shapes.group import Group
238
+ from batoolset.drawings.shapes.serializablefont import SerializableQFont
239
+ from batoolset.drawings.shapes.serializableguiparams import SerializableGUIparameters
240
+
241
+ # print(object_type)
242
+
243
+ if object_type == list.__name__:
244
+ return create_objects_from_dict(properties)
245
+ elif object_type == Image2D.__name__:
246
+ # I can code custom shit in there
247
+ # if not 'args' in properties and 'filename' in properties:
248
+ # properties['args']=properties['filename']
249
+ # print('trying to create an image', properties)
250
+ return Image2D(**properties)
251
+ elif object_type == Line2D.__name__:
252
+ return Line2D(**properties)
253
+ elif object_type == TAText2D.__name__:
254
+ return TAText2D(**properties)
255
+ elif object_type == Point2D.__name__:
256
+ return Point2D(**properties)
257
+ elif object_type == Ellipse2D.__name__:
258
+ return Ellipse2D(**properties)
259
+ elif object_type == Circle2D.__name__:
260
+ return Circle2D(**properties)
261
+ elif object_type == Freehand2D.__name__:
262
+ return Freehand2D(**properties)
263
+ elif object_type == Polygon2D.__name__:
264
+ return Polygon2D(**properties)
265
+ elif object_type == PolyLine2D.__name__:
266
+ return PolyLine2D(**properties)
267
+ elif object_type == Rectangle2D.__name__:
268
+ return Rectangle2D(**properties)
269
+ elif object_type == Square2D.__name__:
270
+ return Square2D(**properties)
271
+ elif object_type == ScaleBar.__name__:
272
+ return ScaleBar(**properties)
273
+ elif object_type == Group.__name__:
274
+ return Group(**properties)
275
+ elif object_type == SerializableQFont.__name__:
276
+ # print('I am in there,', properties)
277
+ return SerializableQFont.from_dict(properties)
278
+ elif object_type == SerializableGUIparameters.__name__:
279
+ return SerializableGUIparameters(**properties)
280
+ else:
281
+ print(f'unknown type for deserialization {object_type}')
282
+
283
+
284
+ def is_float(str):
285
+ try:
286
+ float(str)
287
+ return True
288
+ except Exception:
289
+ return False
290
+
291
+
292
+ def repair_legacy_xml(xml_string):
293
+ """Fix known corruptions written by old serializer versions so legacy .pyf
294
+ files can still be opened:
295
+ - raw unescaped <!DOCTYPE ...> lines inside <text> elements
296
+ - closing tags that lost their final '>'
297
+ Only call this after ET.fromstring failed on the original string."""
298
+ import re
299
+ fixed = re.sub(r'<!DOCTYPE[^>]*>',
300
+ lambda m: m.group(0).replace('<', '&lt;').replace('>', '&gt;'),
301
+ xml_string)
302
+ fixed = re.sub(r'(</[A-Za-z_][A-Za-z0-9_]*)(\s*\n)', r'\1>\2', fixed)
303
+ return fixed
304
+
305
+
306
+ def _fromstring_with_repair(xml_string):
307
+ try:
308
+ return ET.fromstring(xml_string)
309
+ except ET.ParseError:
310
+ # retry once with legacy fix-ups; if this also fails the ParseError propagates
311
+ return ET.fromstring(repair_legacy_xml(xml_string))
312
+
313
+
314
+ def deserialize_to_dict(xml_string):
315
+ root = _fromstring_with_repair(xml_string)
316
+ return element_to_dict(root)
317
+
318
+
319
+ def process_value(value):
320
+ if value in ['None', 'True', 'False']:
321
+ if value == 'None':
322
+ value = None
323
+ elif value == 'True':
324
+ value = True
325
+ elif value == 'False':
326
+ value = False
327
+ elif isinstance(value, str) and value.startswith('""') and value.endswith('""'):
328
+ value = value[2:-2]
329
+ elif isinstance(value, str) and value.startswith('[') and value.endswith(']'):
330
+ value = safe_literal_eval(value)
331
+ elif value.isdigit():
332
+ value = int(value)
333
+ elif is_float(value):
334
+ value = float(value)
335
+ return value
336
+
337
+
338
+ def element_to_dict_neo(element):
339
+ properties = {}
340
+
341
+ for child in element:
342
+ tag = child.tag
343
+ value = None
344
+
345
+ if list(child): # check if current element has any sub-elements
346
+ # If the tag is 'annotations', we want to store a list of dictionaries
347
+ if tag == 'annotations':
348
+ value = []
349
+ for grandchild in child:
350
+ grandchild_dict = element_to_dict(grandchild)
351
+ value.append(grandchild_dict)
352
+ # For other tags with sub-elements, we store a single dictionary
353
+ else:
354
+ value = {}
355
+ for grandchild in child:
356
+ if grandchild.tag == 'metadata' or grandchild.tag == 'positions' or grandchild.tag == 'annotations':
357
+ continue
358
+ else:
359
+ grandchild_dict = element_to_dict(grandchild)
360
+ value.update(grandchild_dict)
361
+ else:
362
+ value = child.text
363
+
364
+ # Process the value
365
+ value = process_value(value)
366
+
367
+ # If tag already exists in properties, append the new value to the list
368
+ if tag in properties:
369
+ if not isinstance(properties[tag], list):
370
+ properties[tag] = [properties[tag]]
371
+ properties[tag].append(value)
372
+ # If tag does not exist, add it with the new value
373
+ else:
374
+ properties[tag] = value
375
+
376
+ return properties
377
+
378
+ def element_to_dict(element):
379
+ """{tag: value} of the children of *element* (a repeated tag gives a list).
380
+
381
+ When different tags are INTERLEAVED (a figure's top level or a group's
382
+ content: Group, Image2D, Group...) a plain dict puts all the Groups
383
+ before all the Image2Ds and the figure came back reordered (undo,
384
+ reopen). Such elements are returned as an OrderedChildren: the same dict
385
+ plus the document order, which create_objects_from_dict() follows."""
386
+ properties = {}
387
+ ordered = []
388
+ interleaved = False
389
+
390
+ for child in element:
391
+ tag = child.tag
392
+
393
+ # print('tag',tag)
394
+
395
+ if list(child): # check if current element has any sub-elements
396
+
397
+ if tag == 'annotations':
398
+ value = []
399
+ for grandchild in child:
400
+ grandchild_dict = element_to_dict(grandchild)
401
+ value.append({grandchild.tag:grandchild_dict})
402
+ else:
403
+ # Recursively parse sub-elements and store the resulting dictionary
404
+ # print('tag2',tag)
405
+ value = element_to_dict(child) # the second entrance create the pb
406
+ #
407
+ # if tag == 'annotations':
408
+ # print('babidi', value) #
409
+ else:
410
+ value = child.text
411
+
412
+ if value is None:
413
+ value = {}
414
+ for grandchild in child:
415
+ if grandchild.tag == 'metadata':
416
+ value = safe_literal_eval(grandchild.text)
417
+ elif grandchild.tag == 'positions':
418
+ # print('INSIDE POSITIONS', grandchild.text)
419
+ value[grandchild.tag] = safe_literal_eval(grandchild.text)
420
+ elif grandchild.tag == 'annotations':
421
+ # print('inside annotations', grandchild)
422
+ pass
423
+ else:
424
+ value[grandchild.tag] = element_to_dict(grandchild)
425
+ else:
426
+ if isinstance(value, dict):
427
+ # If value is a dictionary, leave it as is
428
+ # print('ignored', tag, value)
429
+ # --> the annotations are ignored and they should not
430
+ # print('value ignored', tag, len(value)) # this is the dict of the values of the annotations --> somehow I need to take them
431
+ pass
432
+ elif value in ['None', 'True', 'False']:
433
+ if value == 'None':
434
+ value = None
435
+ elif value == 'True':
436
+ value = True
437
+ elif value == 'False':
438
+ value = False
439
+ elif isinstance(value, str) and value.startswith('""') and value.endswith('""'):
440
+ value = value[2:-2]
441
+ elif isinstance(value, str) and value.startswith('[') and value.endswith(']'):
442
+ value = safe_literal_eval(value) # recreate the list from string (no code execution)
443
+ elif isinstance(value, list):
444
+ pass
445
+ elif value.isdigit():
446
+ value = int(value)
447
+ elif is_float(value):
448
+ value = float(value)
449
+
450
+ # if tag == 'annotations':
451
+ # print('annotations--> ',tag, value)
452
+ # properties[tag] = value # if it contains it already it will override it --> that is the error
453
+ if tag in properties and ordered and ordered[-1][0] != tag:
454
+ interleaved = True
455
+ ordered.append((tag, value))
456
+ # Check if tag already exists in properties
457
+ if tag in properties:
458
+ # If tag already exists, check if the value is a list
459
+ if not isinstance(properties[tag], list):
460
+ # If the value is not a list, convert it to a list
461
+ properties[tag] = [properties[tag]]
462
+ # Append the new value to the list
463
+ properties[tag].append(value)
464
+ else:
465
+ # If tag does not exist, add it with the new value
466
+ properties[tag] = value
467
+
468
+ if interleaved:
469
+ return OrderedChildren(properties, ordered)
470
+ return properties
471
+
472
+
473
+ class OrderedChildren(dict):
474
+ """{tag: value(s)} like any element_to_dict result (callers reading it
475
+ as a dict are unchanged), plus ``.order``: the children as (tag, value)
476
+ in document order, which create_objects_from_dict() follows."""
477
+
478
+ def __init__(self, properties, order):
479
+ super().__init__(properties)
480
+ self.order = list(order)
481
+
482
+
483
+ def has_custom_script(xml_string):
484
+ root = _fromstring_with_repair(xml_string)
485
+
486
+ # Find the custom_loading_script element anywhere in the XML
487
+ custom_script_tags = root.findall(".//custom_loading_script")
488
+
489
+ for custom_script_tag in custom_script_tags:
490
+ custom_script = custom_script_tag.text
491
+ if custom_script is not None and custom_script.strip() != 'None' and custom_script.strip():
492
+ return True
493
+
494
+ return False
495
+
496
+ if __name__ == '__main__':
497
+ # from batoolset.serializations.tools import object_to_xml, create_object
498
+ from batoolset.settings.global_settings import set_UI # set the UI to qtpy
499
+
500
+ set_UI()
501
+ from batoolset.drawings.shapes.image2d import Rectangle2D
502
+ from qtpy.QtCore import QRect, Qt, QRectF
503
+ from batoolset.drawings.shapes.image2d import Image2D
504
+ import numpy as np
505
+ import sys
506
+ from qtpy.QtWidgets import QApplication
507
+ from qtpy.QtGui import QFont
508
+ from batoolset.drawings.shapes.group import Group
509
+ from batoolset.drawings.shapes.serializablefont import SerializableQFont
510
+
511
+ app = QApplication(sys.argv) # IMPORTANT KEEP !!!!!!!!!!!
512
+
513
+ if True:
514
+ img = Image2D('/E/Sample_images/sample_images_FIJI/first-instar-brain.tif')
515
+ # img.dimensions = {'order': 'tdhw', 'values': [15, 35]}
516
+ # img.LUTs = [None,'GREEN']
517
+ img.normalization = {
518
+ "min_range": 'toto',
519
+ "min_value": 0,
520
+ "max_range": 'tutu',
521
+ "max_value": 255,
522
+ "per_channel": True
523
+ }
524
+
525
+ print(img.normalization)
526
+
527
+ print(img.to_dict()['normalization'])
528
+ print(type(img.to_dict()['normalization']))
529
+
530
+ clone = clone_object(img)
531
+
532
+ print(clone.normalization)
533
+ print(type(clone.normalization))
534
+ print(clone.normalization == img.normalization)
535
+
536
+ print(clone.normalization['min_value']+1) # ça marche
537
+
538
+
539
+ sys.exit(0)
540
+
541
+ if True:
542
+ img = Image2D('/E/Sample_images/sample_images_FIJI/first-instar-brain.tif')
543
+ # img.dimensions = {'order': 'tdhw', 'values': [15, 35]}
544
+ # img.LUTs = [None,'GREEN']
545
+ img.projection = 'max'
546
+
547
+ print(img.projection)
548
+
549
+
550
+ print(img.to_dict()['projection'])
551
+ print(type(img.to_dict()['projection']))
552
+
553
+ clone = clone_object(img)
554
+
555
+ print(clone.projection)
556
+ print(clone.projection == img.projection)
557
+
558
+
559
+ sys.exit(0)
560
+
561
+ if True:
562
+ img = Image2D('/E/Sample_images/sample_images_FIJI/first-instar-brain.tif')
563
+ # img.dimensions = {'order': 'tdhw', 'values': [15, 35]}
564
+ # img.LUTs = [None,'GREEN']
565
+ img.channels = [True, False]
566
+
567
+ print(img.channels)
568
+
569
+
570
+ print(img.to_dict()['channels'])
571
+ print(type(img.to_dict()['channels']))
572
+
573
+ clone = clone_object(img)
574
+
575
+ print(clone.channels)
576
+ print(clone.channels == img.channels)
577
+
578
+
579
+ sys.exit(0)
580
+
581
+
582
+ if False:
583
+ img = Image2D('/E/Sample_images/sample_images_FIJI/confocal-series.tif')
584
+ # img.dimensions = {'order': 'tdhw', 'values': [15, 35]}
585
+ img.LUTs = [None,'GREEN']
586
+
587
+
588
+ print(img.LUTs)
589
+
590
+
591
+ print(img.to_dict()['LUTs'])
592
+ print(type(img.to_dict()['LUTs']))
593
+
594
+ clone = clone_object(img)
595
+
596
+ print(clone.LUTs)
597
+ print(clone.LUTs == img.LUTs)
598
+
599
+
600
+ sys.exit(0)
601
+
602
+
603
+ if True:
604
+
605
+ img = Image2D('/E/Sample_images/sample_images_denoise_manue/29-1_lif/ON 290119.lif - Series003.tif')
606
+ img.dimensions = {'order': 'tdhw', 'values': [15, 35]}
607
+
608
+
609
+
610
+ # print(img.to_dict()['dimensions'])
611
+ print(img.dimensions)
612
+
613
+
614
+ clone = clone_object(img)
615
+ print(clone.dimensions)
616
+
617
+ print(clone.dimensions == img.dimensions)
618
+ print(type(clone.dimensions), type(img.dimensions))
619
+
620
+ # very good -−> I need do some crazy stuff in qimage if not that
621
+
622
+
623
+
624
+
625
+
626
+
627
+ sys.exit(0)
628
+
629
+
630
+ if False:
631
+ script = """
632
+ t = np.arange(0.0, 2.0, 0.01)
633
+ s = 1 + np.sin(2 * np.pi * t)
634
+ fig, ax = plt.subplots()
635
+ print(type(fig))
636
+ ax.plot(t, s)
637
+ ax.set(xlabel='time (s)', ylabel='voltage (mV)', title=None)
638
+ ax.grid()
639
+ self.img = fig
640
+ """
641
+
642
+ # IF INDENT OF SCRIPT IS INCORRECT THE THETA IS SET PROPERLY OTHERWISE IT'S NOT
643
+ # matplotlib_figure = Image2D(fig)
644
+
645
+ matplotlib_figure = Image2D(custom_loading_script=script, theta=-45) # if theta is set from there then it is not ok # the theta from there does not reach to the end --> due to super override
646
+ # without rotation they are all ok --> so I need to do the translation of the rotation - the non rotated
647
+ # matplotlib_figure.set_rotation(-45)
648
+
649
+ print('qsdsqdqsdqsdqssqd', matplotlib_figure.theta) # this is reset but where # --> not set if from there
650
+ print(matplotlib_figure.to_dict()['theta']) # why is this 0 ???
651
+ print(clone_object(matplotlib_figure).to_dict()['theta']) # after cloning it is lost but why is that
652
+
653
+ sys.exit(0)
654
+
655
+
656
+ if False:
657
+ # very good --> I can also clone it --> see how I can add it to the stuff and then reload it
658
+
659
+ font = SerializableQFont()
660
+ font.setFamily('Arial')
661
+ font.setFamily('Comic Sans MS')
662
+ font.setPointSize(12)
663
+ font.setWeight(QFont.Normal)
664
+ font.setItalic(True)
665
+ font.background = 0xFF0000
666
+ font.foreground = 0x0000FF
667
+ font.placement = Position('top-right')
668
+
669
+ # Convert the SerializableQFont object to a dictionary
670
+ font_dict = font.to_dict()
671
+
672
+ xml_of_object =object_to_xml(font)
673
+ print(xml_of_object) # seems good
674
+
675
+ deser = deserialize_to_dict(xml_of_object) # this is also ok
676
+ print('deser',deser)
677
+
678
+ print(type(font).__name__)
679
+
680
+
681
+
682
+ clone = clone_object(font)
683
+
684
+ print(font_dict)
685
+ print(clone.to_dict())
686
+ print(clone.to_dict() == font_dict)
687
+
688
+
689
+
690
+ sys.exit(0)
691
+
692
+ if False:
693
+ txt = TAText2D('',placement=Position('center_h center_v'), range=[0,1])
694
+ print(object_to_xml(txt))
695
+ clone = clone_object(txt)
696
+ print(txt, clone) # -−> bingo this stuff is not clone properly if text is empty then it is deserialized as a list [] , also true for empty spaces but as soon as it is containing any non white space the n it gest cloned properly --> how can I fix that ???
697
+
698
+
699
+
700
+
701
+ sys.exit(0)
702
+
703
+
704
+
705
+ if False:
706
+
707
+ txt = TAText2D('this is a test',placement=Position('center_h center_v'), range=[0,1])
708
+
709
+ print(txt.range)
710
+
711
+ print('dabu',type(txt.to_dict()['range']))
712
+
713
+
714
+ print(object_to_xml(txt))
715
+
716
+ clone = clone_object(txt)
717
+
718
+ print(clone.range, type(clone.range)) # there is an error
719
+
720
+
721
+ print(clone.to_dict() == txt.to_dict()) # they are not the same but why --> is it because of placement ???
722
+
723
+ sys.exit(0)
724
+
725
+
726
+
727
+
728
+
729
+ if False:
730
+ xml_with_custom_script = """
731
+ <root>
732
+ <custom_loading_script>&quot;&quot;
733
+ import pandas as pd
734
+ import matplotlib.pyplot as plt
735
+ import numpy as np
736
+
737
+ # Read data using pandas
738
+ file_path = '/E/Sample_images/EZF_SF_scientifig_EZFig/sample_images_SF/sample_test_ScientFig/sample_data.txt'
739
+ data = pd.read_csv(file_path, sep=' ')
740
+
741
+ # Replace comma with dot in 'Percentages' and 'Errors' columns
742
+ data['Percentages'] = data['Percentages'].str.replace(',', '.').astype(float)
743
+ data['Errors'] = data['Errors'].str.replace(',', '.').astype(float)
744
+
745
+ # Plotting
746
+ fig, ax = plt.subplots()
747
+
748
+ for genotype, group in data.groupby('Genotype'):
749
+ ax.errorbar(group['Polygon Class'], group['Percentages'], yerr=group['Errors'], label=genotype, marker='o')
750
+
751
+ ax.set_xlabel('Polygon Class')
752
+ ax.set_ylabel('Percentages')
753
+ ax.set_title('Polygon Class vs Percentages')
754
+ ax.legend()
755
+ plt.grid(True)
756
+
757
+ self.img=fig
758
+ &quot;&quot;</custom_loading_script>
759
+ </root>
760
+ """
761
+
762
+ xml_without_custom_script_1 = """
763
+ <root>
764
+ <custom_loading_script>None</custom_loading_script>
765
+ </root>
766
+ """
767
+
768
+ xml_without_custom_script_2 = """
769
+ <root>
770
+ <custom_loading_script/>
771
+ <custom_loading_script>None</custom_loading_script>
772
+ <custom_loading_script>None</custom_loading_script>
773
+ <custom_loading_script>None</custom_loading_script>
774
+ </root>
775
+ """
776
+
777
+ print(has_custom_script(xml_with_custom_script)) # Output: True
778
+ print(has_custom_script(xml_without_custom_script_1)) # Output: False
779
+ print(has_custom_script(xml_without_custom_script_2)) # Output: False
780
+
781
+ print(has_custom_script(read_string_from_file('/tmp/tmpppa9xdax/state_0.xml')))
782
+ print(has_custom_script(read_string_from_file('/tmp/tmpppa9xdax/state_1.xml')))
783
+ print(has_custom_script(read_string_from_file('/home/aigouy/Bureau/test.pyf')))
784
+ print(has_custom_script(read_string_from_file('/home/aigouy/Bureau/test_with_custom_script.pyf')))
785
+
786
+ img = Image2D(width=512, height=512)
787
+ img3 = Image2D(width=512, height=512)
788
+ print(has_custom_script(object_to_xml(img)))
789
+
790
+ img2 = Image2D(custom_loading_script='bqlsqjdqksdjlqskdlkqsj')
791
+ print(has_custom_script(object_to_xml(img2)))
792
+
793
+
794
+
795
+
796
+ grp = Group(img, img2)
797
+
798
+ # print(object_to_xml(grp))
799
+
800
+ print(has_custom_script(object_to_xml(grp))) # still does not work
801
+
802
+ grp = Group(img, img3, Group(img, img3))
803
+ print(has_custom_script(object_to_xml(grp))) # still does not work
804
+
805
+ grp = Group(img, img3, Group(img, Group(img2)))
806
+ print(has_custom_script(object_to_xml(grp))) # still does not work
807
+ sys.exit(0)
808
+
809
+
810
+ if True:
811
+ # img0 = Image2D(width=512, height=512, annotations=TAText2D('this is a test of your system', placement='center_h center_v'))
812
+ # img0 = Image2D(width=512, height=512,
813
+ # annotations=[TAText2D('second', placement='center_h center_v', text_orientation='-Y')],
814
+ # isText=True)
815
+
816
+ tmp2 = Image2D(width=512, height=512,
817
+ annotations=[TAText2D('second', placement='center_h center_v', text_orientation='-Y'), Rectangle2D(0,0,120,150), TAText2D('third', placement='center_h center_v', text_orientation='-Y')],
818
+ isText=True)
819
+ print(object_to_xml(tmp2))
820
+
821
+ copy = clone_object(tmp2)
822
+
823
+
824
+
825
+
826
+
827
+ print(type(copy.annotations), copy.annotations) # BUG HERE --> THE ORDER IS CHANGED -−> I NEED A PLAN B!!!
828
+ print(type(copy.img))
829
+ print(type(copy))
830
+ print(copy.isText)
831
+
832
+
833
+
834
+
835
+
836
+ sys.exit(0)
837
+
838
+
839
+ if True:
840
+ img0 = Image2D('/E/Sample_images/EZF_SF_scientifig_EZFig/sample_images_svg/test_asym_size.png')
841
+ img1 = Image2D('/E/Sample_images/EZF_SF_scientifig_EZFig/sample_images_svg/test.png')
842
+
843
+ img3 = Image2D('/E/Sample_images/EZF_SF_scientifig_EZFig/sample_images_svg/test.png')
844
+ img4 = Image2D('/E/Sample_images/EZF_SF_scientifig_EZFig/sample_images_svg/test.png')
845
+
846
+ grp = Group(img3, img4)
847
+
848
+ lst = [img0, img1, grp]
849
+
850
+ # try to serialize this stuff --> this can be seen as an image --> TODO
851
+ xml_string = object_to_xml(lst, is_list=True)
852
+
853
+ # print(xml_string)
854
+
855
+ properties = deserialize_to_dict(xml_string)
856
+ # print(properties)
857
+ clone = create_object(list.__name__, properties)
858
+
859
+ print(clone)
860
+
861
+ # now try to deserialize it
862
+
863
+ sys.exit(0)
864
+
865
+ if True:
866
+
867
+ if False:
868
+ rect = Rectangle2D(10, 16, 200, 512)
869
+ obj_type = Rectangle2D.__name__
870
+
871
+ if False:
872
+ rect = Ellipse2D(10, 16, 200, 512)
873
+ obj_type = Ellipse2D.__name__
874
+
875
+ if False:
876
+ rect = Circle2D(10, 16, 200)
877
+ obj_type = Circle2D.__name__
878
+
879
+ if False:
880
+ rect = Point2D(10, 16)
881
+ obj_type = Point2D.__name__
882
+
883
+ if False:
884
+ rect = Line2D(10, 16, 200, 512)
885
+ obj_type = Line2D.__name__
886
+
887
+ if False:
888
+ rect = Polygon2D(100, 100, 110, 100, 110, 120, 10, 120, 100, 100)
889
+ obj_type = Polygon2D.__name__
890
+
891
+ if False:
892
+ rect = PolyLine2D(100, 100, 110, 100, 110, 120, 10, 120, 100, 100)
893
+ obj_type = PolyLine2D.__name__
894
+
895
+ if False: # no clue why they are ot equal because they seem to be...
896
+ rect = Square2D(10, 16, 200)
897
+ obj_type = Square2D.__name__
898
+
899
+ if False:
900
+ rect = Freehand2D(100, 100, 110, 100, 110, 120, 10, 120, 100, 100)
901
+ obj_type = Freehand2D.__name__
902
+
903
+ if False:
904
+ rect = TAText2D(
905
+ 'this is a test of your system') # they are nbot equal because of the doc --> no big deal and expected
906
+ obj_type = TAText2D.__name__
907
+
908
+ if False:
909
+ rect = TAText2D('this is a test of your system',
910
+ placement='top-left') # they are nbot equal because of the doc --> no big deal and expected
911
+ obj_type = TAText2D.__name__
912
+
913
+ if False:
914
+ rect = ScaleBar(10, legend='10µm',
915
+ placement='top-left') # they are nbot equal because of the doc --> no big deal and expected
916
+ obj_type = ScaleBar.__name__
917
+
918
+ if False:
919
+ rect = Image2D('/E/Sample_images/EZF_SF_scientifig_EZFig/sample_images_svg/test.png')
920
+ obj_type = Image2D.__name__
921
+
922
+ test_text = '''
923
+ </head><body style=" font-family:'Comic Sans MS'; font-size:22pt; font-weight:400; font-style:normal;">
924
+ <p style="color:#00ff00;"><span style=" color:#ff0000;">toto</span><br />tu<span style=" vertical-align:super;">tu</span></p>
925
+ '''
926
+ test_text2 = '''
927
+ </head><body style=" font-family:'Comic Sans MS'; font-size:22pt; font-weight:400; font-style:normal;">
928
+ <p style="color:#00ff00;"><span style=" color:#0000ff;">tititi</span>
929
+ '''
930
+ test_text3 = '''
931
+ </head><body style=" font-family:'Comic Sans MS'; font-size:22pt; font-weight:400; font-style:normal;">
932
+ <p style="color:#00ff00;"><span style=" color:#00ffff;">tata</span>
933
+ '''
934
+
935
+ if False:
936
+ img0 = Image2D('/E/Sample_images/EZF_SF_scientifig_EZFig/sample_images_svg/test_asym_size.png')
937
+ img1 = Image2D('/E/Sample_images/EZF_SF_scientifig_EZFig/sample_images_svg/test.png')
938
+ rect = img1
939
+ obj_type = Image2D.__name__
940
+
941
+ if True:
942
+ if True:
943
+ img1.border_size = 3 # make sure it displays an edge
944
+ # img1.annotations = []
945
+ img1.annotations.append(img0) # add an inset
946
+
947
+ if True:
948
+ # img1.setLettering(TAText2D(text=test_text))
949
+ img1.annotations.append(TAText2D(text=test_text, placement='top-left'))
950
+ img1.annotations.append(TAText2D(text=test_text2, placement='top-left'))
951
+ img1.annotations.append(Line2D(0, 0, img1.width() / 2, img1.height() / 2, stroke=3, color=0xFF00FF))
952
+ img1.annotations.append(Rectangle2D(128, 128, 64, 128, stroke=3, color=0xFF00FF))
953
+ img1.annotations.append(Point2D(img1.width() / 2, img1.height() / 2, stroke=6, color=0xFF00FF))
954
+ img1.annotations.append(
955
+ Ellipse2D(img1.width() / 2 - img1.width() / 8, img1.height() / 2 - img1.height() / 12,
956
+ img1.width() / 4, img1.height() / 6, stroke=3, color=0xFF00FF))
957
+ img1.annotations.append(
958
+ Circle2D(img1.width() / 2 - img1.width() / 10, img1.height() / 2 - img1.height() / 10,
959
+ img1.width() / 5, stroke=3, color=0xFF00FF))
960
+ img1.annotations.append(
961
+ Polygon2D(100, 100, 110, 100, 110, 120, 10, 120, 100, 100, color=0x0000FF, fill_color=0x00FFFF,
962
+ stroke=2))
963
+ img1.annotations.append(PolyLine2D(10, 16, 20, 10, 20, 30, 40, 30, color=0xFF0000, stroke=2))
964
+ img1.annotations.append(PolyLine2D(10, 16, 20, 10, 20, 30, 40, 30, color=0xFF0000, stroke=2))
965
+ img1.annotations.append(Square2D(72.5, 151.5, 30, stroke=3))
966
+ # img1.annotations.append(Freehand2D(10, 10, 20, 10, 20, 30, 288, 30, color=0xFFFF00, stroke=3))
967
+ img1.annotations.append(
968
+ Freehand2D(75, 25, 116.50, 50, 116.50, 100, 75, 125, 33.50, 100, 33.50, 50, color=0xFFFF00,
969
+ stroke=3)) # a beautiful hexagon !!!
970
+ # try a floating text
971
+ img1.annotations.append(TAText2D(x=img1.width() / 2, y=img1.height() / 2,
972
+ text='<font color="#FFFF00">free floating text</font>'))
973
+ img1.annotations.append(ScaleBar(30, '<font color="#FF00FF">right µm</font>',
974
+ placement='top-right')) # here I specify the bar to be top right specifically
975
+
976
+ if False:
977
+ img0 = Image2D('/E/Sample_images/EZF_SF_scientifig_EZFig/sample_images_svg/test_asym_size.png')
978
+ img1 = Image2D('/E/Sample_images/EZF_SF_scientifig_EZFig/sample_images_svg/test.png')
979
+ img1.annotations.append(TAText2D(text=test_text, placement='top-left'))
980
+ img1.annotations.append(TAText2D(text=test_text2, placement='top-left'))
981
+ img1.annotations.append(Line2D(0, 0, img1.width() / 2, img1.height() / 2, stroke=3, color=0xFF00FF))
982
+ img1.annotations.append(Rectangle2D(128, 128, 64, 128, stroke=3, color=0xFF00FF))
983
+ rect = Group(img0, img1)
984
+ obj_type = Group.__name__
985
+
986
+ rect.translate(120, 160)
987
+ if isinstance(rect, Image2D):
988
+ print('init Image', rect.img.shape)
989
+
990
+ print('to_dict orig', rect.to_dict())
991
+
992
+ # if True:
993
+ # print(type(rect.to_dict()['coords_as_list']), rect.to_dict()['coords_as_list'], rect.to_dict()['coords_as_list'][0])
994
+ # all is ok --> error is somewhere else then
995
+
996
+ print('orig', rect)
997
+
998
+ print('orig rect', rect.getRect())
999
+
1000
+ xml_string = object_to_xml(rect)
1001
+
1002
+ if False:
1003
+ print('#' * 20)
1004
+ print(xml_string)
1005
+ print('#' * 20)
1006
+
1007
+ properties = deserialize_to_dict(xml_string)
1008
+ if False:
1009
+ print(properties)
1010
+ print(len(properties['annotations'])) # only 12 --> that is not the pb
1011
+ print(properties['annotations'])
1012
+ for elm, value in properties['annotations'].items():
1013
+ if elm == 'TAText2D':
1014
+ print(elm, len(value), type(value), type(value[
1015
+ 1])) # value is a list of dict --> bingo I wil manage soon # ok so there are 14 stored in there and that is what I missed --> try to fix it -> this is a list so that is why I failed
1016
+ # print(value)
1017
+ for txt in value: # why is first value None ???
1018
+ print(txt)
1019
+ print(value)
1020
+ sys.exit(0)
1021
+
1022
+ clone = create_object(obj_type, properties)
1023
+
1024
+ if isinstance(clone, Image2D):
1025
+ print('copy Image', clone.img)
1026
+
1027
+ print('clone', clone)
1028
+
1029
+ print('to_dict clone', clone.to_dict())
1030
+ print('clone rect', clone.getRect())
1031
+
1032
+ print('equal', rect == clone)
1033
+
1034
+ print('types', type(rect), type(clone))
1035
+
1036
+ try:
1037
+ print('initial', rect.x(), rect.y(), rect.width(), rect.height()) # all ok
1038
+ print('clone', clone.x(), clone.y(), clone.width(), clone.height()) # all 0
1039
+ except Exception:
1040
+ pass # no big deal if fails
1041
+
1042
+ success = False
1043
+ try:
1044
+ print('equal dict', rect.__dict__ == clone.__dict__)
1045
+ success = rect.__dict__ == clone.__dict__
1046
+ except Exception:
1047
+ print('skipping equal dict')
1048
+
1049
+ if not success:
1050
+ for k1, v1 in rect.__dict__.items():
1051
+ for k2, v2 in clone.__dict__.items():
1052
+ if k1 == k2:
1053
+ if k1 in ['doc', 'img']:
1054
+ continue
1055
+ try:
1056
+ if v1 != v2:
1057
+ if isinstance(v2, Position):
1058
+ if str(v2) == str(v1):
1059
+ continue
1060
+ print('not same values for', k1, v1, 'vs', v2, type(v1), type(v2))
1061
+ except Exception:
1062
+ print(v1, 'vs', v2)
1063
+
1064
+ xml_of_clone = object_to_xml(clone)
1065
+ print('serialization equality', xml_string == xml_of_clone)
1066
+
1067
+ if False:
1068
+ if xml_string != xml_of_clone:
1069
+ print('#' * 30)
1070
+ print(xml_of_clone)
1071
+ print('#' * 30)
1072
+
1073
+ import difflib
1074
+
1075
+ # string1 = "This is the first string."
1076
+ # string2 = "This is the second string."
1077
+
1078
+ diff = difflib.ndiff(xml_string.splitlines(), xml_of_clone.splitlines())
1079
+
1080
+ print('difference:')
1081
+ print(" vs ".join(diff))
1082
+ # for dif in diff:
1083
+ # print(dif)
1084
+ # print(diff)
1085
+
1086
+ if isinstance(rect, Image2D):
1087
+ print('annitations nb', len(rect.annotations),
1088
+ len(clone.annotations)) # 15 texts are missing --> the error is in deserialization because serilaization is ok
1089
+
1090
+ sys.exit(0)
1091
+
1092
+ if False: # old
1093
+ # if this is a string --> make it save as '''value''' -−> unpack it --> maybe need an extra check for a count of the values or maybe use """ or maybe use something unique so that it does not compete with python such as two '' or two "" at beginning and at the end
1094
+
1095
+ img1 = Image2D('/E/Sample_images/EZF_SF_scientifig_EZFig/sample_images_svg/test.png')
1096
+ img0 = Image2D('/E/Sample_images/EZF_SF_scientifig_EZFig/sample_images_svg/test_asym_size.png')
1097
+ test_text = '''
1098
+ </head><body style=" font-family:'Comic Sans MS'; font-size:22pt; font-weight:400; font-style:normal;">
1099
+ <p style="color:#00ff00;"><span style=" color:#ff0000;">toto</span><br />tu<span style=" vertical-align:super;">tu</span></p>
1100
+ '''
1101
+ test_text2 = '''
1102
+ </head><body style=" font-family:'Comic Sans MS'; font-size:22pt; font-weight:400; font-style:normal;">
1103
+ <p style="color:#00ff00;"><span style=" color:#0000ff;">tititi</span>
1104
+ '''
1105
+ test_text3 = '''
1106
+ </head><body style=" font-family:'Comic Sans MS'; font-size:22pt; font-weight:400; font-style:normal;">
1107
+ <p style="color:#00ff00;"><span style=" color:#00ffff;">tata</span>
1108
+ '''
1109
+
1110
+ if True:
1111
+ if True:
1112
+ img1.border_size = 3 # make sure it displays an edge
1113
+ # img1.annotations = []
1114
+ img1.annotations.append(img0) # add an inset
1115
+
1116
+ if True:
1117
+ # img1.setLettering(TAText2D(text=test_text))
1118
+ img1.annotations.append(TAText2D(text=test_text, placement='top-left'))
1119
+ img1.annotations.append(TAText2D(text=test_text2, placement='top-left'))
1120
+ img1.annotations.append(TAText2D(text=test_text3, placement='top-left'))
1121
+
1122
+ img1.annotations.append(TAText2D(text=test_text, placement='bottom-right'))
1123
+ img1.annotations.append(TAText2D(text=test_text2, placement='bottom-right'))
1124
+ img1.annotations.append(TAText2D(text=test_text3, placement='bottom-right'))
1125
+
1126
+ img1.annotations.append(TAText2D(text=test_text, placement='bottom-left'))
1127
+ img1.annotations.append(TAText2D(text=test_text2, placement='bottom-left'))
1128
+
1129
+ img1.annotations.append(TAText2D(text=test_text, placement='top-right'))
1130
+ img1.annotations.append(TAText2D(text=test_text2, placement='top-right'))
1131
+
1132
+ img1.annotations.append(TAText2D(text=test_text, placement='center_h-center_v'))
1133
+ img1.annotations.append(TAText2D(text=test_text2, placement='center_h-center_v'))
1134
+
1135
+ # print('TADAM',img1.width()/2., img1.height()/2.)
1136
+ img1.annotations.append(Line2D(0, 0, img1.width() / 2, img1.height() / 2, stroke=3, color=0xFF00FF))
1137
+ img1.annotations.append(Rectangle2D(128, 128, 64, 128, stroke=3, color=0xFF00FF))
1138
+ img1.annotations.append(Point2D(img1.width() / 2, img1.height() / 2, stroke=6, color=0xFF00FF))
1139
+ img1.annotations.append(
1140
+ Ellipse2D(img1.width() / 2 - img1.width() / 8, img1.height() / 2 - img1.height() / 12,
1141
+ img1.width() / 4, img1.height() / 6, stroke=3, color=0xFF00FF))
1142
+ img1.annotations.append(
1143
+ Circle2D(img1.width() / 2 - img1.width() / 10, img1.height() / 2 - img1.height() / 10,
1144
+ img1.width() / 5, stroke=3, color=0xFF00FF))
1145
+ img1.annotations.append(
1146
+ Polygon2D(100, 100, 110, 100, 110, 120, 10, 120, 100, 100, color=0x0000FF, fill_color=0x00FFFF,
1147
+ stroke=2))
1148
+ img1.annotations.append(PolyLine2D(10, 16, 20, 10, 20, 30, 40, 30, color=0xFF0000, stroke=2))
1149
+ img1.annotations.append(PolyLine2D(10, 16, 20, 10, 20, 30, 40, 30, color=0xFF0000, stroke=2))
1150
+ img1.annotations.append(Square2D(72.5, 151.5, 30, stroke=3))
1151
+ # img1.annotations.append(Freehand2D(10, 10, 20, 10, 20, 30, 288, 30, color=0xFFFF00, stroke=3))
1152
+ img1.annotations.append(
1153
+ Freehand2D(75, 25, 116.50, 50, 116.50, 100, 75, 125, 33.50, 100, 33.50, 50, color=0xFFFF00,
1154
+ stroke=3)) # a beautiful hexagon !!!
1155
+
1156
+ # try a floating text
1157
+ img1.annotations.append(TAText2D(text='<font color="#FF0000">top left by coords</font>'))
1158
+ img1.annotations.append(TAText2D(x=img1.width() / 2, y=img1.height() / 2,
1159
+ text='<font color="#FFFF00">free floating text</font>'))
1160
+ img1.annotations.append(
1161
+ ScaleBar(30, '<font color="#FF00FF">10µm</font>')) # default scale bar placement is bottom right
1162
+ img1.annotations.append(ScaleBar(30, '<font color="#FF00FF">right µm</font>',
1163
+ placement='top-right')) # here I specify the bar to be top right specifically
1164
+ # very good now try
1165
+
1166
+ img1.scale = 0.5
1167
+
1168
+ # print(img1.width())
1169
+ # print(img1.height())
1170
+
1171
+ # print(same_img.__dict__)
1172
+ xml_string = object_to_xml(img1)
1173
+
1174
+ print(xml_string)
1175
+ print('#' * 40)
1176
+
1177
+ # xml_string = '''<?xml version="1.0" ?>
1178
+ # <Rectangle2D>
1179
+ # <color>""16776960""</color>
1180
+ # <fill_color>None</fill_color>
1181
+ # <stroke>0.65</stroke>
1182
+ # <opacity>1.0</opacity>
1183
+ # <isSet>False</isSet>
1184
+ # <scale>1</scale>
1185
+ # <line_style>None</line_style>
1186
+ # <theta>0</theta>
1187
+ # <incompressible_width>0</incompressible_width>
1188
+ # <incompressible_height>0</incompressible_height>
1189
+ # <immutable>False</immutable>
1190
+ # <__init_called__>True</__init_called__>
1191
+ # </Rectangle2D>'''
1192
+
1193
+ # test ='16776960'
1194
+
1195
+ # print(test.isdigit())
1196
+ # print(int(test)+1)
1197
+
1198
+ properties = deserialize_to_dict(xml_string)
1199
+ if 'filename' in properties:
1200
+ properties['args'] = properties['filename']
1201
+ print('#' * 2000)
1202
+ print(properties)
1203
+ print('#-' * 2000)
1204
+
1205
+ # rect = xml_to_object(xml_string)
1206
+ # print(rect.__dict__)
1207
+
1208
+ # print(rect.__dict__ == same_img.__dict__)
1209
+
1210
+ # plt.imshow(rect.img)
1211
+ # plt.show()
1212
+
1213
+ rect = Image2D(**properties)
1214
+ print('testing keys', rect.__dict__.keys() == img1.__dict__.keys())
1215
+
1216
+ # removing the image is required for that
1217
+ # rect.__dict__['img']=None
1218
+ # same_img.__dict__['img']=None
1219
+
1220
+ try:
1221
+ print(rect.__dict__ == img1.__dict__)
1222
+ except Exception:
1223
+ # Check all pairwise comparisons between the dictionaries
1224
+ for k1, v1 in rect.__dict__.items():
1225
+ for k2, v2 in img1.__dict__.items():
1226
+ if k1 == k2:
1227
+ try:
1228
+ if v1 != v2 or abs(v1 - v2) > 1e-9:
1229
+ if isinstance(v2, Position):
1230
+ if str(v2) == str(v1):
1231
+ continue
1232
+
1233
+ print('not same values for', k1, v1, 'vs', v2, type(v1), type(v2))
1234
+ if k1 == 'annotations':
1235
+ # print(type(v1))
1236
+ # k1 = eval(v1)
1237
+ # print(type(k1))
1238
+ if v1:
1239
+ for key, val in v1.items():
1240
+ print(key, '-->', val)
1241
+ try:
1242
+ print(create_object(key, val))
1243
+ except Exception:
1244
+ traceback.print_exc()
1245
+ # bingo -−> can be easy TODO too -−> do deserialize that too!!
1246
+ # else:
1247
+
1248
+ except Exception:
1249
+ if not np.array_equal(v1, v2):
1250
+ print('images', k1, 'not the same')
1251
+ # if not np.array_equal(v1, v2):
1252
+ # print(f"Error: The '{k1}' arrays are not equal.")
1253
+ # print(f"Array 1: {v1}")
1254
+ # print(f"Array 2: {v2}")
1255
+ # print()
1256
+ # pass
1257
+ # else:
1258
+ # print(f"Error: The dictionaries have different keys: '{k1}' and '{k2}'.")
1259
+
1260
+ '''
1261
+ there is a minor bug due to:
1262
+
1263
+ 'placement':<pyfigures.gui.Position.Position object at 0x7fded71d1480>
1264
+ <placement>
1265
+ <positions>{'top': True, 'bottom': False, 'left': False, 'right': True, 'center_h': False, 'center_v': False}</positions>
1266
+ </placement>
1267
+
1268
+ # see how I can fix that
1269
+ '''
1270
+
1271
+ print(rect, img1) # -> that rougly seems to work
1272
+ # --> see how to handle the things that need to be duplicated
1273
+
1274
+ if False:
1275
+ test2 = Rectangle2D(
1276
+ QRectF(10, 0, 100, 256)) # the serialization is great but now I need to see how to reload the shit
1277
+
1278
+ print(test2)
1279
+
1280
+ if True:
1281
+ xml_string = object_to_xml(test2)
1282
+
1283
+ rect = deserialize_rectangle2d(xml_string)
1284
+ print(rect.__dict__)
1285
+
1286
+ print(rect)
1287
+
1288
+ print(
1289
+ rect == test2) # very good --> that works and that is cool --> these are really two objects but they are interchangeable
1290
+ print(test2.__dict__)
1291
+
1292
+ print(test2.__dict__ == rect.__dict__)
1293
+
1294
+ # print
1295
+
1296
+ #
1297
+ #
1298
+ # if __name__ == '__main__':
1299
+ # from batoolset.settings.global_settings import set_UI # set the UI to qtpy
1300
+ #
1301
+ # set_UI()
1302
+ # from batoolset.drawings.shapes.image2d import Rectangle2D
1303
+ # from qtpy.QtCore import QRect, Qt, QRectF
1304
+ #
1305
+ # test2 = Rectangle2D(QRectF(10, 0, 100, 256)) # the serialization is great but now I need to see how to reload the shit
1306
+ # xml_version =object_to_xml(test2)
1307
+ # print(xml_version)
1308
+ #
1309
+ # print('Deserialization')
1310
+ #
1311
+ # # print(xml_to_object(xml_version))
1312
+ #
1313
+ #
1314
+ # if False:
1315
+ # from batoolset.drawings.shapes.image2d import Image2D
1316
+ #
1317
+ #
1318
+ # same_img = Image2D('/E/Sample_images/EZF_SF_scientifig_EZFig/sample_images_svg/test.png')
1319
+ # same_img.scale = 0.5
1320
+ #
1321
+ # print(same_img.width())
1322
+ # print(same_img.height())
1323
+ #
1324
+ #
1325
+ # # when it loops into position it should just get the position as a string
1326
+ #
1327
+ #
1328
+ # # see also how to handle the embedded images -> shall I store them separately or not
1329
+ # # can I use magic unique names to avoid repetitions ???
1330
+ #
1331
+ #
1332
+ #
1333
+ # print(object_to_xml(same_img))
1334
+ #
1335
+ #
1336
+ # # try to reload it maybe
1337
+
1338
+