From a6a827f1df5a1807da40baf1a4782e0857efa120 Mon Sep 17 00:00:00 2001 From: "ekaterina.itsenko" Date: Thu, 15 May 2025 22:31:16 +0000 Subject: [PATCH] [pycharm] PY-80835 PY-81089 Debugger: move cursor and add data type Merge-request: IJ-MR-162745 Merged-by: Ekaterina Itsenko (cherry picked from commit 4481d3e22b0c597c5d72eff21100feb94182977c) GitOrigin-RevId: cb7a8bfce45fec4525e99a29e74ce342485bdf10 --- .../messages/ImagesBundle.properties | 7 ++- .../images/editor/impl/ImageEditorUI.java | 59 +++---------------- .../scientific/utils/ScientificUtils.kt | 1 + .../pydev/_pydevd_bundle/pydevd_tables.py | 2 +- .../tables/images/pydevd_image_loader.py | 8 ++- .../tables/images/pydevd_numpy_based_image.py | 22 ++++--- .../tables/images/pydevd_numpy_image.py | 21 ++++--- .../jetbrains/python/debugger/PyDebugUtils.kt | 2 + .../python/debugger/PyDebugValue.java | 2 +- 9 files changed, 52 insertions(+), 72 deletions(-) diff --git a/images/resources/messages/ImagesBundle.properties b/images/resources/messages/ImagesBundle.properties index 17b7d89783bb..7ea0aa791376 100644 --- a/images/resources/messages/ImagesBundle.properties +++ b/images/resources/messages/ImagesBundle.properties @@ -103,5 +103,8 @@ image.channels.mode.channel.3=Channel 3 image.color.mode.configure.actions=Binarization\u2026 action.restore.original.text=Restore Original action.normalize.image.text=Normalize Image -scientific.cursor.position=(x = {0}, y = {1}); -scientific.pixel.value=value = ({0}, {1}, {2}) \ No newline at end of file +scientific.cursor.position=x={0}, y={1} +scientific.pixel.value.grayscale=Value: {0} +scientific.pixel.value.rgb=Value: {0},{1},{2} +scientific.data.type=Type: {0} +scientific.data.dimensions=HxW:{0}x{1} \ No newline at end of file diff --git a/images/src/org/intellij/images/editor/impl/ImageEditorUI.java b/images/src/org/intellij/images/editor/impl/ImageEditorUI.java index a638f21097a2..2b71c378b280 100644 --- a/images/src/org/intellij/images/editor/impl/ImageEditorUI.java +++ b/images/src/org/intellij/images/editor/impl/ImageEditorUI.java @@ -53,11 +53,11 @@ import org.intellij.images.editor.ImageEditor; import org.intellij.images.editor.ImageZoomModel; import org.intellij.images.editor.actionSystem.ImageEditorActions; import org.intellij.images.options.*; +import org.intellij.images.scientific.utils.ScientificUtils; import org.intellij.images.thumbnail.actionSystem.ThumbnailViewActions; import org.intellij.images.thumbnail.actions.ShowBorderAction; import org.intellij.images.ui.ImageComponent; import org.intellij.images.ui.ImageComponentDecorator; -import org.intellij.images.scientific.utils.ScientificUtils; import org.intellij.images.vfs.IfsUtil; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NonNls; @@ -174,59 +174,19 @@ public final class ImageEditorUI extends JPanel implements UiDataProvider, CopyP contentPanel.add(myScrollPane, IMAGE_PANEL); contentPanel.add(errorPanel, ERROR_PANEL); - JPanel topPanel = new NonOpaquePanel(new BorderLayout()); - JPanel bottomPanel = new NonOpaquePanel(new BorderLayout()); - - infoLabel = new JLabel((String)null, SwingConstants.RIGHT); - infoLabel.setBorder(JBUI.Borders.emptyRight(2)); - boolean isScientificMode = editor != null && editor.getFile().getUserData(ScientificUtils.SCIENTIFIC_MODE_KEY) != null; + JPanel topPanel = new NonOpaquePanel(new BorderLayout()); if (!isEmbedded) { topPanel.add(toolbarPanel, BorderLayout.WEST); - if (isScientificMode) { - JPanel scientificInfoPanel = new NonOpaquePanel(new FlowLayout(FlowLayout.LEFT, 2, 0)); - JLabel positionLabel = new JLabel(ImagesBundle.message("scientific.cursor.position", 0, 0)); - JLabel valueLabel = new JLabel(ImagesBundle.message("scientific.pixel.value", 0, 0, 0)); - scientificInfoPanel.add(positionLabel); - scientificInfoPanel.add(valueLabel); - - view.addMouseMotionListener(new MouseMotionAdapter() { - @Override - public void mouseMoved(MouseEvent e) { - BufferedImage image = imageComponent.getDocument().getValue(); - if (image != null) { - double zoom = imageComponent.getZoomFactor(); - - int x = (int)((e.getX() - ImageComponent.IMAGE_INSETS) / zoom); - int y = (int)((e.getY() - ImageComponent.IMAGE_INSETS) / zoom); - - if (x >= 0 && x < image.getWidth() && y >= 0 && y < image.getHeight()) { - int pixel = image.getRGB(x, y); - int r = (pixel >> 16) & 0xFF; - int g = (pixel >> 8) & 0xFF; - int b = pixel & 0xFF; - positionLabel.setText(ImagesBundle.message("scientific.cursor.position", x, y)); - valueLabel.setText(ImagesBundle.message("scientific.pixel.value", r, g, b)); - } - else { - positionLabel.setText(ImagesBundle.message("scientific.cursor.position", 0, 0)); - valueLabel.setText(ImagesBundle.message("scientific.pixel.value", 0, 0, 0)); - } - } - } - }); - - bottomPanel.add(scientificInfoPanel, BorderLayout.WEST); - bottomPanel.add(infoLabel, BorderLayout.EAST); - } - else { + if (!isScientificMode) { + infoLabel = new JLabel((String)null, SwingConstants.RIGHT); + infoLabel.setBorder(JBUI.Borders.emptyRight(2)); topPanel.add(infoLabel, BorderLayout.EAST); } } add(topPanel, BorderLayout.NORTH); add(contentPanel, BorderLayout.CENTER); - add(bottomPanel, BorderLayout.SOUTH); myScrollPane.addComponentListener(new ComponentAdapter() { @Override @@ -250,21 +210,20 @@ public final class ImageEditorUI extends JPanel implements UiDataProvider, CopyP private void updateInfo() { if (isEmbedded) return; + boolean isScientificMode = editor != null && editor.getFile().getUserData(ScientificUtils.SCIENTIFIC_MODE_KEY) != null; + if (isScientificMode) return; ImageDocument document = imageComponent.getDocument(); BufferedImage image = document.getValue(); - VirtualFile file = editor != null ? editor.getFile() : null; - boolean isScientificMode = (file != null ? file.getUserData(ScientificUtils.SCIENTIFIC_MODE_KEY) : null) != null; if (image != null) { ColorModel colorModel = image.getColorModel(); String format = document.getFormat(); if (format == null) { format = editor != null ? ImagesBundle.message("unknown.format") : ""; - } else if (isScientificMode) { - format = ""; } else { format = StringUtil.toUpperCase(format); } + VirtualFile file = editor != null ? editor.getFile() : null; infoLabel.setText( ImagesBundle.message("image.info", image.getWidth(), image.getHeight(), format, @@ -735,4 +694,4 @@ public final class ImageEditorUI extends JPanel implements UiDataProvider, CopyP imageComponent.setGridLineColor(gridOptions.getLineColor()); } } -} +} \ No newline at end of file diff --git a/images/src/org/intellij/images/scientific/utils/ScientificUtils.kt b/images/src/org/intellij/images/scientific/utils/ScientificUtils.kt index efe0e82ed736..170723bd1624 100644 --- a/images/src/org/intellij/images/scientific/utils/ScientificUtils.kt +++ b/images/src/org/intellij/images/scientific/utils/ScientificUtils.kt @@ -21,6 +21,7 @@ object ScientificUtils { @JvmField var SCIENTIFIC_MODE_KEY: Key = Key("SCIENTIFIC_MODE") val ORIGINAL_IMAGE_KEY: Key = Key("ORIGINAL_IMAGE") + val DATA_TYPE_KEY: Key = Key("DATA_TYPE") val ROTATION_ANGLE_KEY: Key = Key.create("IMAGE_ROTATION_ANGLE") val CURRENT_NOT_NORMALIZED_IMAGE_KEY: Key = Key("CURRENT_NOT_NORMALIZED_IMAGE_KEY") val IS_NORMALIZED_KEY: Key = Key("IS_NORMALIZED") diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_tables.py b/python/helpers/pydev/_pydevd_bundle/pydevd_tables.py index 626a277abec4..c316863e4e87 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_tables.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_tables.py @@ -142,7 +142,7 @@ def __get_image_provider(output): import _pydevd_bundle.tables.images.pydevd_numpy_based_image as image_provider elif type_qualified_name == 'numpy.ndarray': import _pydevd_bundle.tables.images.pydevd_numpy_image as image_provider - elif type_qualified_name == 'PIL.Image.Image': + elif type_qualified_name in ['PIL.Image.Image', 'PIL.PngImagePlugin.PngImageFile', 'PIL.JpegImagePlugin.JpegImageFile']: import _pydevd_bundle.tables.images.pydevd_pillow_image as image_provider elif type_qualified_name == 'matplotlib.figure.Figure': import _pydevd_bundle.tables.images.pydevd_matplotlib_image as image_provider diff --git a/python/helpers/pydev/_pydevd_bundle/tables/images/pydevd_image_loader.py b/python/helpers/pydev/_pydevd_bundle/tables/images/pydevd_image_loader.py index 8f21139da55f..6bfffd3789a4 100644 --- a/python/helpers/pydev/_pydevd_bundle/tables/images/pydevd_image_loader.py +++ b/python/helpers/pydev/_pydevd_bundle/tables/images/pydevd_image_loader.py @@ -32,8 +32,8 @@ def load_image_chunk(offset, image_id): return "Error: {}".format(e) -def save_image_to_storage(image_data, format=DEFAULT_IMAGE_FORMAT, save_func=None): - # type: (any, str, callable) -> str +def save_image_to_storage(image_data, data_type=None, format=DEFAULT_IMAGE_FORMAT, save_func=None): + # type: (any, str, str, callable) -> str try: bytes_buffer = io.BytesIO() try: @@ -45,7 +45,9 @@ def save_image_to_storage(image_data, format=DEFAULT_IMAGE_FORMAT, save_func=Non bytes_data = bytes_buffer.getvalue() image_id = str(uuid.uuid4()) IMAGE_DATA_STORAGE[image_id] = bytes_data - return "{};{}".format(image_id, len(bytes_data)) + if data_type is None: + data_type = "None" + return "{};{};{}".format(image_id, len(bytes_data), data_type) finally: bytes_buffer.close() except Exception as e: diff --git a/python/helpers/pydev/_pydevd_bundle/tables/images/pydevd_numpy_based_image.py b/python/helpers/pydev/_pydevd_bundle/tables/images/pydevd_numpy_based_image.py index 421a5b469c02..bca8e1eca1ca 100644 --- a/python/helpers/pydev/_pydevd_bundle/tables/images/pydevd_numpy_based_image.py +++ b/python/helpers/pydev/_pydevd_bundle/tables/images/pydevd_numpy_based_image.py @@ -16,6 +16,10 @@ def create_image(arr): try: from PIL import Image + if hasattr(arr.dtype, 'name'): + data_type = arr.dtype.name + else: + data_type = arr.dtype arr_to_convert = arr try: @@ -37,14 +41,17 @@ def create_image(arr): elif arr_to_convert.ndim == 3 and arr_to_convert.shape[2] == 1: arr_to_convert = arr_to_convert[:, :, 0] - arr_min, arr_max = np.min(arr_to_convert), np.max(arr_to_convert) - if arr_min == arr_max: # handle constant values - arr_to_convert = np.full_like(arr_to_convert, 127, dtype=np.uint8) - elif 0 <= arr_min <= 1 and 0 <= arr_max <= 1: + arr_min, arr_max = arr_to_convert.min(), arr_to_convert.max() + is_float = np.issubdtype(arr_to_convert.dtype, np.floating) + is_bool = np.issubdtype(arr_to_convert.dtype, np.bool) + + if (is_float or is_bool) and 0 <= arr_min <= 1 and 0 <= arr_max <= 1: # bool and float in [0; 1] arr_to_convert = (arr_to_convert * 255).astype(np.uint8) - elif arr_min < 0 or arr_max > 255: + elif arr_min != arr_max and (arr_min < 0 or arr_max > 255): # other values out of [0; 255] arr_to_convert = ((arr_to_convert - arr_min) * 255 / (arr_max - arr_min)).astype(np.uint8) - else: + elif arr_min == arr_max and (arr_min < 0 or arr_max > 255): + arr_to_convert = (np.ones_like(arr_to_convert) * 127).astype(np.uint8) + else: # values in [0; 255] arr_to_convert = arr_to_convert.astype(np.uint8) arr_to_convert_ndim = arr_to_convert.ndim @@ -54,7 +61,8 @@ def create_image(arr): mode = RGBA_MODE else: mode = RGB_MODE - return save_image_to_storage(Image.fromarray(arr_to_convert, mode=mode)) + + return save_image_to_storage(Image.fromarray(arr_to_convert, mode=mode), data_type=data_type) except ImportError: return "Error: Pillow library is not installed." diff --git a/python/helpers/pydev/_pydevd_bundle/tables/images/pydevd_numpy_image.py b/python/helpers/pydev/_pydevd_bundle/tables/images/pydevd_numpy_image.py index 3a75553d5084..e1fa8348d1dd 100644 --- a/python/helpers/pydev/_pydevd_bundle/tables/images/pydevd_numpy_image.py +++ b/python/helpers/pydev/_pydevd_bundle/tables/images/pydevd_numpy_image.py @@ -7,7 +7,9 @@ def create_image(arr): try: from PIL import Image + data_type = arr.dtype.name arr_to_convert = arr + arr_to_convert = np.where(arr_to_convert == None, 0, arr_to_convert) arr_to_convert = np.nan_to_num(arr_to_convert, nan=0) @@ -19,14 +21,17 @@ def create_image(arr): elif arr_to_convert.ndim == 3 and arr_to_convert.shape[2] == 1: arr_to_convert = arr_to_convert[:, :, 0] - arr_min, arr_max = np.min(arr_to_convert), np.max(arr_to_convert) - if arr_min == arr_max: # handle constant values - arr_to_convert = np.full_like(arr_to_convert, 127, dtype=np.uint8) - elif 0 <= arr_min <= 1 and 0 <= arr_max <= 1: + arr_min, arr_max = arr_to_convert.min(), arr_to_convert.max() + is_float = np.issubdtype(arr_to_convert.dtype, np.floating) + is_bool = np.issubdtype(arr_to_convert.dtype, np.bool) + + if (is_float or is_bool) and 0 <= arr_min <= 1 and 0 <= arr_max <= 1: # bool and float in [0; 1] arr_to_convert = (arr_to_convert * 255).astype(np.uint8) - elif arr_min < 0 or arr_max > 255: - arr_to_convert = ((arr_to_convert - arr_min) * 255 / (arr_max - arr_min)).astype(np.uint8) - else: + elif arr_min != arr_max and (arr_min < 0 or arr_max > 255): + arr_to_convert = ((arr_to_convert - arr_min) * 255 / (arr_max - arr_min)).astype(np.uint8) # other values out of [0; 255] + elif arr_min == arr_max and (arr_min < 0 or arr_max > 255): + arr_to_convert = (np.ones_like(arr_to_convert) * 127).astype(np.uint8) + else: # values in [0; 255] arr_to_convert = arr_to_convert.astype(np.uint8) arr_to_convert_ndim = arr_to_convert.ndim @@ -37,7 +42,7 @@ def create_image(arr): else: mode = RGB_MODE - return save_image_to_storage(Image.fromarray(arr_to_convert, mode=mode)) + return save_image_to_storage(Image.fromarray(arr_to_convert, mode=mode), data_type=data_type) except ImportError: return "Error: Pillow library is not installed." diff --git a/python/pydevSrc/src/com/jetbrains/python/debugger/PyDebugUtils.kt b/python/pydevSrc/src/com/jetbrains/python/debugger/PyDebugUtils.kt index 757397cd0235..d4956f281c96 100644 --- a/python/pydevSrc/src/com/jetbrains/python/debugger/PyDebugUtils.kt +++ b/python/pydevSrc/src/com/jetbrains/python/debugger/PyDebugUtils.kt @@ -35,6 +35,8 @@ object NodeTypes { const val SPARSE_TENSOR_NODE_TYPE: String = "SparseTensor" const val TENSOR_NODE_TYPE: String = "Tensor" const val IMAGE_NODE_TYPE: String = "Image" + const val PNG_IMAGE_NODE_TYPE: String = "PngImageFile" + const val JPEG_IMAGE_NODE_TYPE: String = "JpegImageFile" const val FIGURE_NODE_TYPE: String = "Figure" const val DATA_FRAME_NODE_TYPE: String = "DataFrame" const val SERIES_NODE_TYPE: String = "Series" diff --git a/python/pydevSrc/src/com/jetbrains/python/debugger/PyDebugValue.java b/python/pydevSrc/src/com/jetbrains/python/debugger/PyDebugValue.java index d86a8d9a8071..4fafbce266dd 100644 --- a/python/pydevSrc/src/com/jetbrains/python/debugger/PyDebugValue.java +++ b/python/pydevSrc/src/com/jetbrains/python/debugger/PyDebugValue.java @@ -539,7 +539,7 @@ public class PyDebugValue extends XNamedValue { int[] shape = extractShape(debugValue); yield isValidArrayShape(shape); } - case NodeTypes.IMAGE_NODE_TYPE, NodeTypes.FIGURE_NODE_TYPE -> true; + case NodeTypes.IMAGE_NODE_TYPE, NodeTypes.PNG_IMAGE_NODE_TYPE, NodeTypes.JPEG_IMAGE_NODE_TYPE, NodeTypes.FIGURE_NODE_TYPE -> true; default -> false; }; }