interactive-pipe 0.8.6__tar.gz → 0.8.7__tar.gz

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 (61) hide show
  1. {interactive_pipe-0.8.6/src/interactive_pipe.egg-info → interactive_pipe-0.8.7}/PKG-INFO +6 -4
  2. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/pyproject.toml +1 -1
  3. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/readme.md +4 -2
  4. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/__init__.py +7 -2
  5. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/gui.py +41 -0
  6. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/qt_gui.py +30 -5
  7. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/headless/control.py +28 -0
  8. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/helper/choose_backend.py +4 -0
  9. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7/src/interactive_pipe.egg-info}/PKG-INFO +6 -4
  10. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/LICENSE +0 -0
  11. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/setup.cfg +0 -0
  12. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/core/__init__.py +0 -0
  13. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/core/cache.py +0 -0
  14. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/core/engine.py +0 -0
  15. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/core/filter.py +0 -0
  16. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/core/graph.py +0 -0
  17. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/core/pipeline.py +0 -0
  18. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/core/signature.py +0 -0
  19. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/data_objects/__init__.py +0 -0
  20. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/data_objects/audio.py +0 -0
  21. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/data_objects/curves.py +0 -0
  22. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/data_objects/data.py +0 -0
  23. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/data_objects/image.py +0 -0
  24. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/data_objects/parameters.py +0 -0
  25. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/__init__.py +0 -0
  26. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/gradio_control.py +0 -0
  27. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/gradio_gui.py +0 -0
  28. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/mpl_control.py +0 -0
  29. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/mpl_gui.py +0 -0
  30. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/mpl_window.py +0 -0
  31. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/nb_control.py +0 -0
  32. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/nb_gui.py +0 -0
  33. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/qt_control.py +0 -0
  34. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/window.py +0 -0
  35. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/headless/__init__.py +0 -0
  36. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/headless/keyboard.py +0 -0
  37. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/headless/pipeline.py +0 -0
  38. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/helper/__init__.py +0 -0
  39. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/helper/_private.py +0 -0
  40. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/helper/control_abbreviation.py +0 -0
  41. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/helper/filter_decorator.py +0 -0
  42. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/helper/keyword_args_analyzer.py +0 -0
  43. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/helper/pipeline_decorator.py +0 -0
  44. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/thirdparty/images_openai_api.py +0 -0
  45. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe/thirdparty/music_spotify.py +0 -0
  46. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe.egg-info/SOURCES.txt +0 -0
  47. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe.egg-info/dependency_links.txt +0 -0
  48. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe.egg-info/requires.txt +0 -0
  49. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/src/interactive_pipe.egg-info/top_level.txt +0 -0
  50. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/test/test_cache.py +0 -0
  51. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/test/test_control_abbreviations.py +0 -0
  52. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/test/test_controller.py +0 -0
  53. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/test/test_core.py +0 -0
  54. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/test/test_curves.py +0 -0
  55. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/test/test_decorator.py +0 -0
  56. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/test/test_engine.py +0 -0
  57. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/test/test_filter.py +0 -0
  58. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/test/test_headless.py +0 -0
  59. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/test/test_image.py +0 -0
  60. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/test/test_parameters.py +0 -0
  61. {interactive_pipe-0.8.6 → interactive_pipe-0.8.7}/test/test_recorder.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: interactive_pipe
3
- Version: 0.8.6
3
+ Version: 0.8.7
4
4
  Summary: Library to create flexible interactive image processing pipelines and automatically add a graphical user interface without knowing anything about GUI coding!
5
5
  Author-email: Balthazar Neveu <balthazarneveu@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/balthazarneveu/interactive_pipe
@@ -49,6 +49,7 @@ Requires-Dist: gradio; extra == "full"
49
49
  |:--:|
50
50
  |Quick setup `pip install interactive-pipe` |
51
51
  | [Project website](https://balthazarneveu.github.io/interactive_pipe/) |
52
+ |[![Interactive pipe python package](https://github.com/balthazarneveu/interactive_pipe/actions/workflows/pytest.yaml/badge.svg)](https://github.com/balthazarneveu/interactive_pipe/actions/workflows/pytest.yaml) |
52
53
 
53
54
  </center>
54
55
 
@@ -106,18 +107,19 @@ pip install -e ".[full]"
106
107
 
107
108
  ## :scroll: Features
108
109
 
109
- **Version 0.8.6**
110
+ **Version 0.8.7**
110
111
  - Modular multi-image processing filters
111
112
  - Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
112
113
  - Support in jupyter notebooks
113
114
  - Tuning sliders & check buttons with a GUI
114
115
  - Cache intermediate results in RAM for much faster processing
115
116
  - `KeyboardControl` : no slider on UI but exactly the same internal mechanism, update on key press.
116
- - Support Curve plots (2D signals)
117
+ - Support Curve plots (2D signalKeybos)
117
118
  - :new: gradio backend (+allows sharing with others).
118
119
  - :new: Audio support in Gradio (live audio or display several players by returning 1D numpy arrays)
119
120
  - :new: Circular sliders for Qt Backend
120
121
  - :new: Text prompt (`free_text=("Hello world!", None),`)
122
+ - :new: TimeControl (possibility to play/pause time using an incrementing timer)
121
123
 
122
124
 
123
125
 
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "interactive_pipe"
8
- version = "0.8.6"
8
+ version = "0.8.7"
9
9
  authors = [
10
10
  { name="Balthazar Neveu", email="balthazarneveu@gmail.com" },
11
11
  ]
@@ -5,6 +5,7 @@
5
5
  |:--:|
6
6
  |Quick setup `pip install interactive-pipe` |
7
7
  | [Project website](https://balthazarneveu.github.io/interactive_pipe/) |
8
+ |[![Interactive pipe python package](https://github.com/balthazarneveu/interactive_pipe/actions/workflows/pytest.yaml/badge.svg)](https://github.com/balthazarneveu/interactive_pipe/actions/workflows/pytest.yaml) |
8
9
 
9
10
  </center>
10
11
 
@@ -62,18 +63,19 @@ pip install -e ".[full]"
62
63
 
63
64
  ## :scroll: Features
64
65
 
65
- **Version 0.8.6**
66
+ **Version 0.8.7**
66
67
  - Modular multi-image processing filters
67
68
  - Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
68
69
  - Support in jupyter notebooks
69
70
  - Tuning sliders & check buttons with a GUI
70
71
  - Cache intermediate results in RAM for much faster processing
71
72
  - `KeyboardControl` : no slider on UI but exactly the same internal mechanism, update on key press.
72
- - Support Curve plots (2D signals)
73
+ - Support Curve plots (2D signalKeybos)
73
74
  - :new: gradio backend (+allows sharing with others).
74
75
  - :new: Audio support in Gradio (live audio or display several players by returning 1D numpy arrays)
75
76
  - :new: Circular sliders for Qt Backend
76
77
  - :new: Text prompt (`free_text=("Hello world!", None),`)
78
+ - :new: TimeControl (possibility to play/pause time using an incrementing timer)
77
79
 
78
80
 
79
81
 
@@ -1,6 +1,11 @@
1
- __version__ = "0.8.6"
1
+ __version__ = "0.8.7"
2
2
  from interactive_pipe.helper.pipeline_decorator import pipeline, interactive_pipeline
3
- from interactive_pipe.headless.control import Control, CircularControl, TextPrompt
3
+ from interactive_pipe.headless.control import (
4
+ Control,
5
+ CircularControl,
6
+ TextPrompt,
7
+ TimeControl,
8
+ )
4
9
  from interactive_pipe.data_objects.curves import Curve, SingleCurve
5
10
  from interactive_pipe.data_objects.image import Image
6
11
  from interactive_pipe.headless.keyboard import KeyboardControl
@@ -2,9 +2,11 @@ from interactive_pipe.headless.pipeline import HeadlessPipeline
2
2
  from interactive_pipe.data_objects.image import Image
3
3
  from interactive_pipe.data_objects.parameters import Parameters
4
4
  from interactive_pipe.headless.keyboard import KeyboardControl
5
+ from interactive_pipe.headless.control import TimeControl
5
6
  import logging
6
7
  from typing import Callable, List
7
8
  from functools import partial
9
+ import time
8
10
 
9
11
 
10
12
  class InteractivePipeGUI:
@@ -86,6 +88,8 @@ class InteractivePipeGUI:
86
88
  return results
87
89
 
88
90
  def bind_key(self, key, func: Callable):
91
+ if key in self.key_bindings.keys():
92
+ logging.warning(f"Key {key} already bound to {self.key_bindings[key]}")
89
93
  self.key_bindings[key] = func
90
94
 
91
95
  def bind_key_to_context(self, key: str, context_param_name: str, doc: str):
@@ -147,7 +151,44 @@ class InteractivePipeGUI:
147
151
  self.bind_key(keyboard_key, update_func)
148
152
  update_func.__doc__ = doc
149
153
 
154
+ def play_pause_time(self, suspend_resume_timer: Callable = None):
155
+ """Play or pause the timer"""
156
+ if self.start_time is None:
157
+ self.start_time = time.time()
158
+ else:
159
+ self.start_time = time.time() - self.start_time
160
+ self.time_playing = not self.time_playing
161
+ if suspend_resume_timer is not None:
162
+ suspend_resume_timer(not self.time_playing)
163
+
164
+ def plug_timer_control(
165
+ self,
166
+ ctrl: TimeControl,
167
+ update_parameter_func: Callable,
168
+ suspend_resume_timer: Callable = None,
169
+ ):
170
+ """Plug timer control to the processing block (pass the time to the block)
171
+ Bind the pause_resume_key (default "p") play/pause key to the timer
172
+ """
173
+ assert isinstance(ctrl, TimeControl)
174
+ doc = ""
175
+ slider_name = ctrl.name
176
+ doc = None
177
+ self.start_time = time.time()
178
+ self.time_playing = True
179
+
180
+ def update_func():
181
+ if self.start_time is None:
182
+ self.start_time = time.time()
183
+ delta_time = time.time() - self.start_time
184
+ return update_parameter_func(slider_name, delta_time)
185
+
186
+ self.bind_key(ctrl.pause_resume_key, partial(self.play_pause_time, suspend_resume_timer))
187
+ update_func.__doc__ = doc
188
+ return update_func
189
+
150
190
  # ---------------------------------------------------------------------
191
+
151
192
  def reset_parameters(self):
152
193
  """reset parameters"""
153
194
  logging.debug("Reset parameters")
@@ -8,7 +8,7 @@ from interactive_pipe.headless.keyboard import KeyboardControl
8
8
  from interactive_pipe.graphical.qt_control import ControlFactory
9
9
  from interactive_pipe.graphical.window import InteractivePipeWindow
10
10
  from interactive_pipe.graphical.gui import InteractivePipeGUI
11
- from interactive_pipe.headless.control import Control
11
+ from interactive_pipe.headless.control import Control, TimeControl
12
12
  import logging
13
13
 
14
14
  PYQTVERSION = None
@@ -27,7 +27,7 @@ if not PYQTVERSION:
27
27
  QHBoxLayout,
28
28
  QMessageBox,
29
29
  )
30
- from PyQt6.QtCore import QUrl, Qt
30
+ from PyQt6.QtCore import QUrl, Qt, QTimer
31
31
  from PyQt6.QtMultimedia import QAudioOutput, QMediaPlayer
32
32
  from PyQt6.QtGui import QPixmap, QImage
33
33
 
@@ -46,7 +46,7 @@ if not PYQTVERSION:
46
46
  QHBoxLayout,
47
47
  QMessageBox,
48
48
  )
49
- from PyQt5.QtCore import QUrl, Qt
49
+ from PyQt5.QtCore import QUrl, Qt, QTimer
50
50
  from PyQt5.QtGui import QPixmap, QImage
51
51
  from PyQt5.QtMultimedia import QMediaPlayer, QAudioOutput, QMediaContent
52
52
 
@@ -68,7 +68,7 @@ if not PYQTVERSION:
68
68
  QHBoxLayout,
69
69
  QMessageBox,
70
70
  )
71
- from PySide6.QtCore import QUrl, Qt # noqa: F811
71
+ from PySide6.QtCore import QUrl, Qt, QTimer # noqa: F811
72
72
  from PySide6.QtMultimedia import QAudioOutput, QMediaPlayer # noqa: F811
73
73
  from PySide6.QtGui import QPixmap, QImage # noqa: F811
74
74
 
@@ -141,6 +141,10 @@ class InteractivePipeQT(InteractivePipeGUI):
141
141
  """reset sliders to default parameters"""
142
142
  super().reset_parameters()
143
143
  for widget_idx, ctrl in self.window.ctrl.items():
144
+ if isinstance(ctrl, TimeControl):
145
+ # self.suspend_resume_timer(True)
146
+ # self.time_playing = False
147
+ self.start_time = None # Reset the timer
144
148
  ctrl.value = ctrl.value_default
145
149
  self.window.reset_sliders()
146
150
 
@@ -382,6 +386,24 @@ class MainWindow(QWidget, InteractivePipeWindow):
382
386
  self.ctrl[slider_name] = ctrl
383
387
  if isinstance(ctrl, KeyboardControl):
384
388
  self.main_gui.bind_keyboard_slider(ctrl, self.key_update_parameter)
389
+ elif isinstance(ctrl, TimeControl):
390
+ self.timer = QTimer(self)
391
+
392
+ def suspend_resume_timer(suspend: bool):
393
+
394
+ if suspend:
395
+ logging.debug("Suspend")
396
+ self.timer.stop()
397
+ else:
398
+ logging.debug("Resume")
399
+ self.timer.start()
400
+ self.main_gui.suspend_resume_timer = suspend_resume_timer
401
+ plugged_func = self.main_gui.plug_timer_control(
402
+ ctrl, self.update_parameter, suspend_resume_timer
403
+ )
404
+ self.timer.timeout.connect(plugged_func)
405
+ self.timer.start(ctrl.update_interval_ms)
406
+
385
407
  elif isinstance(ctrl, Control):
386
408
  slider_instance = control_factory.create_control(
387
409
  ctrl, self.update_parameter
@@ -462,7 +484,10 @@ class MainWindow(QWidget, InteractivePipeWindow):
462
484
  elif self.ctrl[idx]._type == bool:
463
485
  self.ctrl[idx].update(bool(value))
464
486
  elif self.ctrl[idx]._type == float:
465
- self.ctrl[idx].update(self.ctrl[idx].convert_int_to_value(value))
487
+ if isinstance(self.ctrl[idx], TimeControl):
488
+ self.ctrl[idx].update(value)
489
+ else:
490
+ self.ctrl[idx].update(self.ctrl[idx].convert_int_to_value(value))
466
491
  elif self.ctrl[idx]._type == int:
467
492
  self.ctrl[idx].update(value)
468
493
  else:
@@ -214,6 +214,7 @@ class TextPrompt(Control):
214
214
  filter_to_connect: Optional[FilterCore] = None,
215
215
  parameter_name_to_connect: Optional[str] = None,
216
216
  ) -> None:
217
+ """Text box"""
217
218
  super().__init__(
218
219
  value_default=value_default,
219
220
  value_range=None,
@@ -226,3 +227,30 @@ class TextPrompt(Control):
226
227
 
227
228
  def __repr__(self) -> str:
228
229
  return super().__repr__()
230
+
231
+
232
+ class TimeControl(Control):
233
+ def __init__(
234
+ self,
235
+ name=None,
236
+ update_interval_ms: int = 1000,
237
+ pause_resume_key: str = "p",
238
+ filter_to_connect: Optional[FilterCore] = None,
239
+ parameter_name_to_connect: Optional[str] = None,
240
+
241
+ ) -> None:
242
+ """Time control. Start at 0.0. Time can be paused/resumed"""
243
+ super().__init__(
244
+ value_default=0.0,
245
+ value_range=[0.0, 3600.],
246
+ name=name,
247
+ step=None,
248
+ filter_to_connect=filter_to_connect,
249
+ parameter_name_to_connect=parameter_name_to_connect,
250
+ icons=None,
251
+ )
252
+ self.update_interval_ms = update_interval_ms
253
+ self.pause_resume_key = pause_resume_key
254
+
255
+ def __repr__(self) -> str:
256
+ return super().__repr__()
@@ -1,4 +1,5 @@
1
1
  from interactive_pipe.helper import _private
2
+ import signal
2
3
  import logging
3
4
 
4
5
 
@@ -35,6 +36,9 @@ def get_interactive_pipeline_class(gui="auto"):
35
36
  else:
36
37
  selected_gui = gui
37
38
  if selected_gui == "qt":
39
+ # make sure that the signal handlers are reset to default
40
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
41
+ signal.signal(signal.SIGTERM, signal.SIG_DFL)
38
42
  from interactive_pipe.graphical.qt_gui import ( # noqa: F811
39
43
  InteractivePipeQT as ChosenGui,
40
44
  )
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: interactive_pipe
3
- Version: 0.8.6
3
+ Version: 0.8.7
4
4
  Summary: Library to create flexible interactive image processing pipelines and automatically add a graphical user interface without knowing anything about GUI coding!
5
5
  Author-email: Balthazar Neveu <balthazarneveu@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/balthazarneveu/interactive_pipe
@@ -49,6 +49,7 @@ Requires-Dist: gradio; extra == "full"
49
49
  |:--:|
50
50
  |Quick setup `pip install interactive-pipe` |
51
51
  | [Project website](https://balthazarneveu.github.io/interactive_pipe/) |
52
+ |[![Interactive pipe python package](https://github.com/balthazarneveu/interactive_pipe/actions/workflows/pytest.yaml/badge.svg)](https://github.com/balthazarneveu/interactive_pipe/actions/workflows/pytest.yaml) |
52
53
 
53
54
  </center>
54
55
 
@@ -106,18 +107,19 @@ pip install -e ".[full]"
106
107
 
107
108
  ## :scroll: Features
108
109
 
109
- **Version 0.8.6**
110
+ **Version 0.8.7**
110
111
  - Modular multi-image processing filters
111
112
  - Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
112
113
  - Support in jupyter notebooks
113
114
  - Tuning sliders & check buttons with a GUI
114
115
  - Cache intermediate results in RAM for much faster processing
115
116
  - `KeyboardControl` : no slider on UI but exactly the same internal mechanism, update on key press.
116
- - Support Curve plots (2D signals)
117
+ - Support Curve plots (2D signalKeybos)
117
118
  - :new: gradio backend (+allows sharing with others).
118
119
  - :new: Audio support in Gradio (live audio or display several players by returning 1D numpy arrays)
119
120
  - :new: Circular sliders for Qt Backend
120
121
  - :new: Text prompt (`free_text=("Hello world!", None),`)
122
+ - :new: TimeControl (possibility to play/pause time using an incrementing timer)
121
123
 
122
124
 
123
125