[pycharm] PY-80835 PY-81089 Debugger: move cursor and add data type

Merge-request: IJ-MR-162745
Merged-by: Ekaterina Itsenko <ekaterina.itsenko@jetbrains.com>
(cherry picked from commit 4481d3e22b0c597c5d72eff21100feb94182977c)

GitOrigin-RevId: cb7a8bfce45fec4525e99a29e74ce342485bdf10
This commit is contained in:
ekaterina.itsenko
2025-05-15 22:31:16 +00:00
committed by intellij-monorepo-bot
parent 757a45f40f
commit a6a827f1df
9 changed files with 52 additions and 72 deletions

View File

@@ -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})
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}

View File

@@ -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());
}
}
}
}

View File

@@ -21,6 +21,7 @@ object ScientificUtils {
@JvmField
var SCIENTIFIC_MODE_KEY: Key<Unit> = Key<Unit>("SCIENTIFIC_MODE")
val ORIGINAL_IMAGE_KEY: Key<BufferedImage> = Key("ORIGINAL_IMAGE")
val DATA_TYPE_KEY: Key<String> = Key("DATA_TYPE")
val ROTATION_ANGLE_KEY: Key<Int> = Key.create("IMAGE_ROTATION_ANGLE")
val CURRENT_NOT_NORMALIZED_IMAGE_KEY: Key<BufferedImage> = Key("CURRENT_NOT_NORMALIZED_IMAGE_KEY")
val IS_NORMALIZED_KEY: Key<Boolean> = Key<Boolean>("IS_NORMALIZED")

View File

@@ -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

View File

@@ -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:

View File

@@ -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."

View File

@@ -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."

View File

@@ -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"

View File

@@ -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;
};
}