graphica-plot 2.0.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 (181) hide show
  1. graphica/Graphica.ico +0 -0
  2. graphica/__init__.py +1 -0
  3. graphica/__main__.py +111 -0
  4. graphica/assets/__init__.py +0 -0
  5. graphica/assets/icons/__init__.py +0 -0
  6. graphica/assets/icons/arrow-right.svg +21 -0
  7. graphica/assets/icons/bold.svg +20 -0
  8. graphica/assets/icons/calculator.svg +26 -0
  9. graphica/assets/icons/chart-histogram.svg +24 -0
  10. graphica/assets/icons/chart-line.svg +20 -0
  11. graphica/assets/icons/chevron-down.svg +19 -0
  12. graphica/assets/icons/chevron-right.svg +19 -0
  13. graphica/assets/icons/color-swatch.svg +22 -0
  14. graphica/assets/icons/column-insert-right.svg +21 -0
  15. graphica/assets/icons/column-remove.svg +21 -0
  16. graphica/assets/icons/copy.svg +20 -0
  17. graphica/assets/icons/download.svg +21 -0
  18. graphica/assets/icons/edit.svg +21 -0
  19. graphica/assets/icons/eye-off.svg +21 -0
  20. graphica/assets/icons/eye.svg +20 -0
  21. graphica/assets/icons/file-plus.svg +22 -0
  22. graphica/assets/icons/folder-plus.svg +21 -0
  23. graphica/assets/icons/folder.svg +19 -0
  24. graphica/assets/icons/highlight.svg +22 -0
  25. graphica/assets/icons/history.svg +20 -0
  26. graphica/assets/icons/italic.svg +21 -0
  27. graphica/assets/icons/layout-grid.svg +22 -0
  28. graphica/assets/icons/math-function.svg +22 -0
  29. graphica/assets/icons/message-2.svg +21 -0
  30. graphica/assets/icons/mountain.svg +20 -0
  31. graphica/assets/icons/palette.svg +22 -0
  32. graphica/assets/icons/pointer.svg +19 -0
  33. graphica/assets/icons/refresh.svg +20 -0
  34. graphica/assets/icons/row-insert-bottom.svg +21 -0
  35. graphica/assets/icons/row-remove.svg +21 -0
  36. graphica/assets/icons/search.svg +20 -0
  37. graphica/assets/icons/select-all.svg +35 -0
  38. graphica/assets/icons/subscript.svg +20 -0
  39. graphica/assets/icons/superscript.svg +20 -0
  40. graphica/assets/icons/table.svg +21 -0
  41. graphica/assets/icons/trash.svg +23 -0
  42. graphica/assets/icons/typography.svg +23 -0
  43. graphica/assets/icons/x.svg +20 -0
  44. graphica/core/__init__.py +0 -0
  45. graphica/core/analysis.py +1278 -0
  46. graphica/core/app_paths.py +35 -0
  47. graphica/core/axis_settings.py +128 -0
  48. graphica/core/caption_export.py +47 -0
  49. graphica/core/color_palettes.py +56 -0
  50. graphica/core/commands.py +226 -0
  51. graphica/core/cvd_simulation.py +49 -0
  52. graphica/core/dataset.py +443 -0
  53. graphica/core/diagnostics.py +105 -0
  54. graphica/core/excel_utils.py +58 -0
  55. graphica/core/fit_models.py +379 -0
  56. graphica/core/grid_data.py +128 -0
  57. graphica/core/i18n.py +44 -0
  58. graphica/core/json_utils.py +27 -0
  59. graphica/core/label_utils.py +21 -0
  60. graphica/core/methods_text.py +115 -0
  61. graphica/core/named_colors.py +126 -0
  62. graphica/core/plugin_api.py +515 -0
  63. graphica/core/plugin_context.py +167 -0
  64. graphica/core/plugin_install.py +82 -0
  65. graphica/core/plugin_manifest.py +67 -0
  66. graphica/core/plugin_testing.py +213 -0
  67. graphica/core/plugin_types.py +118 -0
  68. graphica/core/provenance.py +19 -0
  69. graphica/core/report_export.py +51 -0
  70. graphica/core/safe_eval.py +194 -0
  71. graphica/core/script_export.py +272 -0
  72. graphica/core/translations_en.py +530 -0
  73. graphica/core/unit_conversion.py +51 -0
  74. graphica/core/update_check.py +43 -0
  75. graphica/core/version.py +14 -0
  76. graphica/gui/__init__.py +0 -0
  77. graphica/gui/app_settings.py +119 -0
  78. graphica/gui/axis_bindings.py +173 -0
  79. graphica/gui/binding.py +135 -0
  80. graphica/gui/builders/__init__.py +1 -0
  81. graphica/gui/builders/axis_panel.py +431 -0
  82. graphica/gui/builders/canvas_area.py +176 -0
  83. graphica/gui/builders/common.py +120 -0
  84. graphica/gui/builders/dataset_panel.py +433 -0
  85. graphica/gui/builders/property_sections.py +208 -0
  86. graphica/gui/canvas.py +519 -0
  87. graphica/gui/color_history.py +47 -0
  88. graphica/gui/color_picker_widget.py +198 -0
  89. graphica/gui/crash_handler.py +77 -0
  90. graphica/gui/cvd_preview.py +23 -0
  91. graphica/gui/data_editor.py +761 -0
  92. graphica/gui/data_import_flow.py +355 -0
  93. graphica/gui/dataset_bindings.py +107 -0
  94. graphica/gui/dataset_style_icon.py +80 -0
  95. graphica/gui/datasets/__init__.py +1 -0
  96. graphica/gui/datasets/actions_menu.py +114 -0
  97. graphica/gui/datasets/colors.py +218 -0
  98. graphica/gui/datasets/fitting.py +70 -0
  99. graphica/gui/datasets/host.py +162 -0
  100. graphica/gui/datasets/operations/__init__.py +1 -0
  101. graphica/gui/datasets/operations/fitting.py +468 -0
  102. graphica/gui/datasets/operations/peaks.py +109 -0
  103. graphica/gui/datasets/operations/processing.py +659 -0
  104. graphica/gui/datasets/operations/runner.py +148 -0
  105. graphica/gui/datasets/operations/transfer.py +227 -0
  106. graphica/gui/datasets/order.py +310 -0
  107. graphica/gui/datasets/overlays.py +74 -0
  108. graphica/gui/datasets/peaks.py +21 -0
  109. graphica/gui/datasets/plugin_runs.py +99 -0
  110. graphica/gui/datasets/processing.py +68 -0
  111. graphica/gui/datasets/property_panel.py +538 -0
  112. graphica/gui/datasets/transfer.py +36 -0
  113. graphica/gui/detached_canvas_window.py +22 -0
  114. graphica/gui/dialogs/__init__.py +137 -0
  115. graphica/gui/dialogs/analysis.py +1322 -0
  116. graphica/gui/dialogs/app.py +1015 -0
  117. graphica/gui/dialogs/appearance.py +851 -0
  118. graphica/gui/dialogs/data_edit.py +577 -0
  119. graphica/gui/dialogs/data_import.py +679 -0
  120. graphica/gui/dialogs/export.py +433 -0
  121. graphica/gui/dock_layout.py +176 -0
  122. graphica/gui/export_preview_panel.py +321 -0
  123. graphica/gui/export_settings.py +16 -0
  124. graphica/gui/file_association.py +97 -0
  125. graphica/gui/icon_utils.py +44 -0
  126. graphica/gui/main_app_window.py +210 -0
  127. graphica/gui/main_window.py +1151 -0
  128. graphica/gui/mathtext_preview.py +168 -0
  129. graphica/gui/menu_bar.py +320 -0
  130. graphica/gui/minimap_widget.py +151 -0
  131. graphica/gui/mixins/__init__.py +0 -0
  132. graphica/gui/mixins/export_mixin.py +520 -0
  133. graphica/gui/mixins/help_mixin.py +127 -0
  134. graphica/gui/mixins/project_io_mixin.py +289 -0
  135. graphica/gui/mixins/quick_access_mixin.py +194 -0
  136. graphica/gui/mixins/ui_setup_mixin.py +206 -0
  137. graphica/gui/notify.py +61 -0
  138. graphica/gui/panels/__init__.py +43 -0
  139. graphica/gui/panels/axis_settings.py +605 -0
  140. graphica/gui/panels/dataset_tree.py +424 -0
  141. graphica/gui/plot_type_drawers.py +131 -0
  142. graphica/gui/plugin_context.py +119 -0
  143. graphica/gui/project_files.py +361 -0
  144. graphica/gui/provenance_panel.py +65 -0
  145. graphica/gui/rendering/__init__.py +1 -0
  146. graphica/gui/rendering/annotations.py +177 -0
  147. graphica/gui/rendering/appearance.py +368 -0
  148. graphica/gui/rendering/common.py +316 -0
  149. graphica/gui/rendering/data_1d.py +376 -0
  150. graphica/gui/rendering/data_2d.py +66 -0
  151. graphica/gui/residual_panel.py +62 -0
  152. graphica/gui/resources.py +16 -0
  153. graphica/gui/single_instance.py +141 -0
  154. graphica/gui/splash.py +83 -0
  155. graphica/gui/task_runner.py +41 -0
  156. graphica/gui/theme.py +864 -0
  157. graphica/gui/tools/__init__.py +56 -0
  158. graphica/gui/tools/annotation.py +275 -0
  159. graphica/gui/tools/cursor.py +259 -0
  160. graphica/gui/tools/layout_edit.py +285 -0
  161. graphica/gui/tools/manager.py +78 -0
  162. graphica/gui/tools/peak_placement.py +132 -0
  163. graphica/gui/tools/range_select.py +191 -0
  164. graphica/gui/tools/region_highlight.py +222 -0
  165. graphica/gui/tools/slice_extraction.py +200 -0
  166. graphica/gui/widget_translation.py +46 -0
  167. graphica/gui/workers.py +280 -0
  168. graphica/models/__init__.py +0 -0
  169. graphica/models/project.py +197 -0
  170. graphica/plugin/__init__.py +20 -0
  171. graphica/plugin/testing.py +57 -0
  172. graphica/sample_data/__init__.py +0 -0
  173. graphica/sample_data/cooling_curve_sample.csv +42 -0
  174. graphica/ui_main_window.py +581 -0
  175. graphica_plot-2.0.0.dist-info/METADATA +317 -0
  176. graphica_plot-2.0.0.dist-info/RECORD +181 -0
  177. graphica_plot-2.0.0.dist-info/WHEEL +5 -0
  178. graphica_plot-2.0.0.dist-info/entry_points.txt +2 -0
  179. graphica_plot-2.0.0.dist-info/licenses/LICENSE +30 -0
  180. graphica_plot-2.0.0.dist-info/licenses/THIRD_PARTY_LICENSES.md +91 -0
  181. graphica_plot-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,198 @@
1
+ """データセットの色の欄: 色見本のボタン(クリックで色を選ぶ)と、#RRGGBB の入力欄。"""
2
+ from PySide6.QtCore import Signal, Qt
3
+ from PySide6.QtGui import QColor, QIcon, QPainter, QPen, QPixmap
4
+ from PySide6.QtWidgets import (QWidget, QHBoxLayout, QPushButton, QLineEdit,
5
+ QMenu)
6
+
7
+ from graphica.gui import notify
8
+ from graphica.core.i18n import tr
9
+ from graphica.core.named_colors import (
10
+ POPUP_LIMIT, NamedColorError, add_named_color, load_named_colors,
11
+ save_named_colors,
12
+ )
13
+ from graphica.gui.color_history import get_color_with_history
14
+
15
+ _SWATCH_ICON_SIZE = 14
16
+
17
+ DEFAULT_COLOR = "#1f77b4"
18
+
19
+
20
+ class ColorPickerWidget(QWidget):
21
+ """colorChanged(str) は利用者の操作で色が変わったときだけ出る(set_color() では出ない)。"""
22
+ colorChanged = Signal(str)
23
+
24
+ def __init__(self, settings, parent=None, initial_color=DEFAULT_COLOR):
25
+ super().__init__(parent)
26
+ self._settings = settings
27
+ self._color = QColor(initial_color)
28
+
29
+ layout = QHBoxLayout(self)
30
+ layout.setContentsMargins(0, 0, 0, 0)
31
+ layout.setSpacing(6)
32
+
33
+ self.swatch_button = QPushButton(self)
34
+ self.swatch_button.setFixedSize(30, 24)
35
+ self.swatch_button.setCursor(Qt.CursorShape.PointingHandCursor)
36
+ self.swatch_button.setToolTip(tr("クリックしてパレットを開く"))
37
+ self.swatch_button.clicked.connect(self._on_swatch_clicked)
38
+
39
+ self.hex_edit = QLineEdit(self)
40
+ self.hex_edit.setPlaceholderText("#RRGGBB")
41
+ self.hex_edit.setMaxLength(7)
42
+ self.hex_edit.editingFinished.connect(self._on_hex_edited)
43
+ self.hex_edit.textChanged.connect(self._on_hex_text_changed)
44
+
45
+ layout.addWidget(self.swatch_button)
46
+ layout.addWidget(self.hex_edit, 1)
47
+
48
+ self._update_swatch()
49
+ self.hex_edit.setText(self._color.name())
50
+
51
+ def color(self):
52
+ return QColor(self._color)
53
+
54
+ def color_name(self):
55
+ return self._color.name()
56
+
57
+ def set_color(self, color):
58
+ """表示だけ変える(colorChanged は出ない)。"""
59
+ new_color = QColor(color)
60
+ if not new_color.isValid():
61
+ return
62
+ self._color = new_color
63
+ self._update_swatch()
64
+ self.hex_edit.blockSignals(True)
65
+ self.hex_edit.setText(self._color.name())
66
+ self.hex_edit.blockSignals(False)
67
+
68
+ def blockSignals(self, block):
69
+ # ほかの欄と同じく blockSignals で値を入れられるよう、中の部品にも伝える
70
+ self.swatch_button.blockSignals(block)
71
+ self.hex_edit.blockSignals(block)
72
+ return super().blockSignals(block)
73
+
74
+ def _update_swatch(self, preview_color=None):
75
+ # preview_color は入力中(未確定)の色。None なら確定した self._color
76
+ from graphica.gui import theme
77
+ border_color = theme.current_tokens()["border_strong"]
78
+ color_name = (preview_color or self._color).name()
79
+ self.swatch_button.setStyleSheet(
80
+ f"background-color: {color_name};"
81
+ f"border: 1px solid {border_color};"
82
+ f"border-radius: 4px;"
83
+ )
84
+
85
+ def refresh_theme(self):
86
+ """ダークモードの切り替えで枠の色を当て直す(塗りはデータの色なのでテーマと関係ない)。"""
87
+ self._update_swatch()
88
+
89
+ def _on_swatch_clicked(self):
90
+ """登録した色(core/named_colors.py)を先に並べ、自由な色選びは「その他の色...」から。"""
91
+ menu = QMenu(self)
92
+ entries = load_named_colors(self._settings)
93
+
94
+ # 登録は際限なく増えるので、並べるのは先頭 POPUP_LIMIT 件まで。残りは検索できる一覧から選ぶ
95
+ for entry in entries[:POPUP_LIMIT]:
96
+ action = menu.addAction(_color_icon(entry["color"]),
97
+ f'{entry["name"]} {entry["color"]}')
98
+ action.setData(entry["color"])
99
+ action.triggered.connect(
100
+ lambda checked=False, c=entry["color"]: self._apply_color_name(c))
101
+ if len(entries) > POPUP_LIMIT:
102
+ more_action = menu.addAction(
103
+ tr("すべての登録色... (%d件)") % len(entries))
104
+ more_action.triggered.connect(self._on_choose_from_all_named_colors)
105
+ if entries:
106
+ menu.addSeparator()
107
+
108
+ register_action = menu.addAction(tr("この色を登録..."))
109
+ register_action.triggered.connect(self._on_register_current_color)
110
+ manage_action = menu.addAction(tr("色名の管理..."))
111
+ manage_action.triggered.connect(self._on_manage_named_colors)
112
+ menu.addSeparator()
113
+ other_action = menu.addAction(tr("その他の色..."))
114
+ other_action.triggered.connect(self._on_pick_other_color)
115
+
116
+ # menu が exec() の間生きていることに頼っている(データセットの右クリックメニューと同じ)
117
+ menu.exec(self.swatch_button.mapToGlobal(
118
+ self.swatch_button.rect().bottomLeft()))
119
+
120
+ def _apply_color_name(self, color_name):
121
+ """値が変わったときだけ知らせる。"""
122
+ candidate = QColor(color_name)
123
+ if not candidate.isValid() or candidate.name() == self._color.name():
124
+ return
125
+ self.set_color(candidate)
126
+ self.colorChanged.emit(self._color.name())
127
+
128
+ def _on_choose_from_all_named_colors(self):
129
+ from graphica.gui.dialogs import NamedColorPickerDialog
130
+ dialog = NamedColorPickerDialog(self._settings, self)
131
+ if dialog.exec() != dialog.DialogCode.Accepted:
132
+ return
133
+ entry = dialog.selected_entry()
134
+ if entry:
135
+ self._apply_color_name(entry["color"])
136
+
137
+ def _on_pick_other_color(self):
138
+ color = get_color_with_history(self._settings, self, initial=self._color)
139
+ if not color.isValid():
140
+ return
141
+ if color.name() == self._color.name():
142
+ return
143
+ self.set_color(color)
144
+ self.colorChanged.emit(self._color.name())
145
+
146
+ def _on_register_current_color(self):
147
+ name, ok = notify.get_text(
148
+ self, tr("色を登録"),
149
+ tr("この色の登録名 (%s)") % self._color.name())
150
+ if not ok:
151
+ return
152
+ entries = load_named_colors(self._settings)
153
+ try:
154
+ entries = add_named_color(entries, name, self._color.name())
155
+ except NamedColorError as e:
156
+ notify.warning(self, tr("色を登録"), str(e))
157
+ return
158
+ save_named_colors(self._settings, entries)
159
+
160
+ def _on_manage_named_colors(self):
161
+ """dialogs がこのモジュールを import しているので、ここで遅れて import する。"""
162
+ from graphica.gui.dialogs import NamedColorManagerDialog
163
+ dialog = NamedColorManagerDialog(self._settings, self)
164
+ dialog.exec()
165
+
166
+ def _on_hex_text_changed(self, text):
167
+ """入力中でも有効な色になったら色見本に先に出す。確定(反映・Undo・colorChanged)は _on_hex_edited だけ(打つたびに Undo を積まないように)。"""
168
+ candidate = QColor(text.strip())
169
+ if candidate.isValid():
170
+ self._update_swatch(preview_color=candidate)
171
+ else:
172
+ self._update_swatch()
173
+
174
+ def _on_hex_edited(self):
175
+ text = self.hex_edit.text().strip()
176
+ candidate = QColor(text)
177
+ if not candidate.isValid():
178
+ # 無効な入力なら今の色の表記に戻す
179
+ self.hex_edit.setText(self._color.name())
180
+ return
181
+ if candidate.name() == self._color.name():
182
+ return
183
+ self.set_color(candidate)
184
+ self.colorChanged.emit(self._color.name())
185
+
186
+
187
+ def _color_icon(color_name, size=_SWATCH_ICON_SIZE):
188
+ """メニューの色見本。白や淡い色が背景に溶けないよう1pxの枠を描く。"""
189
+ pixmap = QPixmap(size, size)
190
+ pixmap.fill(Qt.GlobalColor.transparent)
191
+ painter = QPainter(pixmap)
192
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
193
+ from graphica.gui import theme
194
+ painter.setPen(QPen(QColor(theme.current_tokens()["border_strong"]), 1))
195
+ painter.setBrush(QColor(color_name))
196
+ painter.drawRoundedRect(0, 0, size - 1, size - 1, 3, 3)
197
+ painter.end()
198
+ return QIcon(pixmap)
@@ -0,0 +1,77 @@
1
+ """未処理の例外で、ログの場所とオートセーブからの復元の手順を案内する(exe ではコンソールが無く、何も見えない)。"""
2
+ import sys
3
+ import os
4
+ import logging
5
+ import traceback
6
+
7
+ from PySide6.QtWidgets import QMessageBox
8
+
9
+ from graphica.core.version import APP_NAME, LOG_FILE_NAME
10
+ from graphica.core.app_paths import get_app_data_dir
11
+ from graphica.gui import app_settings
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ _original_excepthook = sys.excepthook
16
+
17
+
18
+ def install_crash_handler():
19
+ sys.excepthook = _handle_uncaught_exception
20
+
21
+
22
+ def _handle_uncaught_exception(exc_type, exc_value, exc_traceback):
23
+ """Ctrl+C(KeyboardInterrupt)は普通の終了なので案内しない。"""
24
+ if issubclass(exc_type, KeyboardInterrupt):
25
+ _original_excepthook(exc_type, exc_value, exc_traceback)
26
+ return
27
+
28
+ logger.critical("未処理の例外が発生しました。", exc_info=(exc_type, exc_value, exc_traceback))
29
+
30
+ log_path = os.path.join(get_app_data_dir(), LOG_FILE_NAME)
31
+ detail = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback))
32
+
33
+ try:
34
+ box = QMessageBox()
35
+ box.setIcon(QMessageBox.Icon.Critical)
36
+ box.setWindowTitle(f"{APP_NAME} - 予期しないエラー")
37
+ box.setText(
38
+ "予期しないエラーが発生しました。\n\n"
39
+ "これまでの作業内容はオートセーブされている可能性があります。"
40
+ f"このまま{APP_NAME}を再起動すると、自動保存からの復元を提案する画面が表示されます。\n\n"
41
+ f"詳細はログファイルを確認してください:\n{log_path}"
42
+ )
43
+ box.setDetailedText(detail)
44
+ box.setStandardButtons(QMessageBox.StandardButton.Ok)
45
+ box.exec()
46
+ except Exception:
47
+ logger.exception("クラッシュ案内ダイアログの表示に失敗しました。")
48
+
49
+
50
+ # セーフモードで起動するかの確認。clean_exit を読むだけで、書き換えるのは PlotterApp だけ
51
+
52
+
53
+ def should_prompt_safe_mode(settings):
54
+ """前回が正常に終わらなかったか。PlotterApp が clean_exit を False にする前に呼ぶこと(後だと自分を異常終了と見誤る)。"""
55
+ return not app_settings.CLEAN_EXIT.read(settings)
56
+
57
+
58
+ def prompt_safe_mode_and_apply(settings, parent=None):
59
+ """前回が異常終了なら、プラグインなしで起動するか尋ねる。セーフモードにしたら True。
60
+
61
+ 既定のボタンは「いいえ」(いつものプラグインが知らないうちに無効になる方が驚く)。
62
+ """
63
+ if not should_prompt_safe_mode(settings):
64
+ return False
65
+
66
+ reply = QMessageBox.question(
67
+ parent, f"{APP_NAME} - セーフモード起動",
68
+ "前回異常終了を検出しました。プラグインを無効にして起動しますか?",
69
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
70
+ QMessageBox.StandardButton.No,
71
+ )
72
+ if reply != QMessageBox.StandardButton.Yes:
73
+ return False
74
+
75
+ from graphica.core.plugin_api import set_safe_mode
76
+ set_safe_mode(True)
77
+ return True
@@ -0,0 +1,23 @@
1
+ """QImage と numpy 配列の変換(色の変換は Qt に依存しない core/cvd_simulation.py)。"""
2
+ import numpy as np
3
+ from PySide6.QtGui import QImage
4
+
5
+ from graphica.core.cvd_simulation import simulate_rgb_array
6
+
7
+
8
+ def simulate_qimage(image, cvd_type):
9
+ """変換した新しい QImage(アルファと元の image は変えない)。"""
10
+ src = image.convertToFormat(QImage.Format.Format_RGBA8888)
11
+ width, height = src.width(), src.height()
12
+ # 行の末尾に詰め物があるので、bytesPerLine() の幅で読んでから width*4 に切り詰める
13
+ bytes_per_line = src.bytesPerLine()
14
+ buf = bytes(src.constBits())
15
+ arr = np.frombuffer(buf, dtype=np.uint8).reshape((height, bytes_per_line))
16
+ arr = arr[:, : width * 4].reshape((height, width, 4)).copy()
17
+
18
+ rgb = arr[:, :, :3].astype(np.float64)
19
+ simulated = np.clip(simulate_rgb_array(rgb, cvd_type), 0, 255).astype(np.uint8)
20
+ arr[:, :, :3] = simulated
21
+
22
+ result = QImage(arr.tobytes(), width, height, width * 4, QImage.Format.Format_RGBA8888)
23
+ return result.copy() # arr のバッファの寿命から切り離す