Title: `ImageItem` not properly scaling along Y-axis with logarithmic scale · Issue #36 · PlotPyStack/PlotPy · GitHub
Open Graph Title: `ImageItem` not properly scaling along Y-axis with logarithmic scale · Issue #36 · PlotPyStack/PlotPy
X Title: `ImageItem` not properly scaling along Y-axis with logarithmic scale · Issue #36 · PlotPyStack/PlotPy
Description: Problem Description I'm experiencing an issue where ImageItem properly stretches across the entire X-axis range but fails to occupy the full Y-axis range when using a logarithmic scale. This behavior is inconsistent between the X and Y a...
Open Graph Description: Problem Description I'm experiencing an issue where ImageItem properly stretches across the entire X-axis range but fails to occupy the full Y-axis range when using a logarithmic scale. This behavi...
X Description: Problem Description I'm experiencing an issue where ImageItem properly stretches across the entire X-axis range but fails to occupy the full Y-axis range when using a logarithmic scale. This be...
Opengraph URL: https://github.com/PlotPyStack/PlotPy/issues/36
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"`ImageItem` not properly scaling along Y-axis with logarithmic scale","articleBody":"_**Problem Description**_\n\nI'm experiencing an issue where ImageItem properly stretches across the entire X-axis range but fails to occupy the full Y-axis range when using a logarithmic scale. This behavior is inconsistent between the X and Y axes.\n\n_**Reproduction Steps**_\n\nI've created a simple test case with a multi-histogram plot that can be rotated to switch the axes:\n- When the logarithmic scale is on the Y-axis (normal orientation), the `ImageItem` doesn't stretch properly from `LOG_MIN` (1) to `LOG_MAX` (10^8).\n- When the same logarithmic scale is on the X-axis (rotated orientation), the `ImageItem` stretches correctly across the full range.\n\n_**Questions**_\n1) Is there something going on with ImageItem's axis handling? Does ImageItem interpret X and Y axes differently when applying scaling?\n2) Does the `QwtScaleEngine` and `QwtTransform` interpret X and Y axes differently? Is there a fundamental difference in how scale engines are applied to different axes?\n\n_**Code Example**_\nI've created a minimal reproducible example that demonstrates the issue. The key components are:\n- Custom `Log10ScaleEngine` and `Log10Transform` for logarithmic scaling.\n- `ImageItem` with properly set bounds in `image_param`.\n- Ability to rotate the plot to switch which axis uses the logarithmic scale. See the `rotate` parameter of the `MultiHistogramPlot` class.\n\n```\nimport sys, numpy as np\nfrom PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QSizePolicy\nfrom PySide6.QtGui import QFont\nfrom PySide6.QtCore import Qt\nfrom plotpy.plot import BasePlot\nfrom plotpy.items import ImageItem\nfrom plotpy.styles import ImageParam\nfrom qwt import QwtScaleDiv, QwtScaleDraw, QwtText\nfrom qwt.scale_engine import QwtScaleEngine\nfrom qwt.transform import QwtTransform\n\nWAVELENGTHS = [\"371nm\", \"382nm\", \"393nm\", \"404nm\", \"415nm\"]\nLOG_MIN, LOG_MAX = 1, 10**8\n\nclass WavelengthTransform(QwtTransform):\n def transform(self, value): return value\n def invTransform(self, value): return value\n def copy(self): return WavelengthTransform()\n\nclass WavelengthScaleDraw(QwtScaleDraw):\n def __init__(self, wavelengths):\n super().__init__()\n self.wavelengths = wavelengths\n self.setLabelRotation(45)\n self.setLabelAlignment(Qt.AlignBottom)\n self.setSpacing(10)\n self.setPenWidth(1.5)\n \n def label(self, value):\n idx = int(round(value))\n if 0 \u003c= idx \u003c len(self.wavelengths):\n text = QwtText(self.wavelengths[idx])\n text.setFont(QFont(\"Arial\", 9))\n return text\n return QwtText(\"\")\n\nclass WavelengthScaleEngine(QwtScaleEngine):\n def __init__(self, wavelengths):\n super().__init__()\n self.wavelengths = wavelengths\n self.setAttribute(QwtScaleEngine.Floating, False)\n self.setAttribute(QwtScaleEngine.Symmetric, False)\n self.setAttribute(QwtScaleEngine.IncludeReference, True)\n \n def transformation(self): return WavelengthTransform()\n def autoScale(self, maxNumSteps, x1, x2, stepSize): return -1, len(self.wavelengths) + 1, 1.0\n \n def divideScale(self, x1, x2, maxMajor, maxMinor, stepSize=0):\n x1, x2 = -1, len(self.wavelengths) + 1\n major_ticks = list(range(len(self.wavelengths)))\n return QwtScaleDiv(x1, x2, [], [], major_ticks)\n\nclass Log10Transform(QwtTransform):\n def __init__(self):\n super().__init__()\n self.LogMin, self.LogMax = LOG_MIN, LOG_MAX\n\n def bounded(self, value): return np.clip(value, self.LogMin, self.LogMax)\n def copy(self): return Log10Transform()\n def invTransform(self, value): return 10**self.bounded(value)\n def transform(self, value): return np.log10(self.bounded(value))\n\nclass Log10ScaleEngine(QwtScaleEngine):\n def transformation(self): return Log10Transform()\n def autoScale(self, maxNumSteps, x1, x2, stepSize): return LOG_MIN, LOG_MAX, 1\n \n def divideScale(self, x1, x2, maxMajor, maxMinor, stepSize=0):\n x1, x2 = LOG_MIN, LOG_MAX\n major_ticks = [10**i for i in range(9)]\n minor_ticks = [base * j for i in range(8) for base in [10**i] for j in range(2, 10)]\n return QwtScaleDiv(x1, x2, minor_ticks, [], major_ticks)\n\nclass MultiHistogramPlot(QMainWindow):\n def __init__(self, rotate=False):\n super().__init__()\n main_widget = QWidget()\n self.setCentralWidget(main_widget)\n layout = QVBoxLayout(main_widget)\n self.rotate = rotate\n self.plot = BasePlot()\n layout.addWidget(self.plot)\n self.setup_plot()\n\n def setup_plot(self):\n self.setup_axes()\n self.num_bins = 1024\n \n # Initialize data array based on orientation\n self.initialH = np.zeros((self.num_bins, len(WAVELENGTHS)) if not self.rotate else (len(WAVELENGTHS), self.num_bins))\n\n # Configure image parameters\n self.image_param = ImageParam()\n self.image_param.lock_position = True\n \n # Set axis limits based on orientation\n if not self.rotate:\n self.image_param.xmin, self.image_param.xmax = -0.5, len(WAVELENGTHS)-0.5\n self.image_param.ymin, self.image_param.ymax = LOG_MIN, LOG_MAX\n else:\n self.image_param.xmin, self.image_param.xmax = LOG_MIN, LOG_MAX\n self.image_param.ymin, self.image_param.ymax = -0.5, len(WAVELENGTHS)-0.5\n \n self.image_param.colormap = \"jet\"\n \n print(f\"Before adding image item: \")\n print(f\"self.image_param.xmin: {self.image_param.xmin}, \\\n self.image_param.xmax: {self.image_param.xmax}, \\\n self.image_param.ymin: {self.image_param.ymin}, \\\n self.image_param.ymax: {self.image_param.ymax}\")\n \n # Create and add image item\n self.hist = ImageItem(self.initialH, self.image_param)\n self.plot.set_aspect_ratio(ratio=None, lock=False) # Prevent overflow errors\n self.plot.add_item(self.hist)\n\n print(f\"After adding image item: \") \n print(f\"self.image_param.xmin: {self.image_param.xmin}, \\\n self.image_param.xmax: {self.image_param.xmax}, \\\n self.image_param.ymin: {self.image_param.ymin}, \\\n self.image_param.ymax: {self.image_param.ymax}\")\n\n \n def setup_axes(self):\n if not self.rotate:\n # Normal orientation\n self.plot.setAxisTitle(BasePlot.X_BOTTOM, \"Wavelength\")\n self.plot.setAxisTitle(BasePlot.Y_LEFT, \"Intensity\")\n self.plot.setAxisScaleEngine(BasePlot.X_BOTTOM, WavelengthScaleEngine(WAVELENGTHS))\n self.plot.setAxisScaleDraw(BasePlot.X_BOTTOM, WavelengthScaleDraw(WAVELENGTHS))\n self.plot.setAxisScaleEngine(BasePlot.Y_LEFT, Log10ScaleEngine())\n else:\n # Rotated orientation\n self.plot.setAxisTitle(BasePlot.X_BOTTOM, \"Intensity\")\n self.plot.setAxisTitle(BasePlot.Y_LEFT, \"Wavelength\")\n self.plot.setAxisScaleEngine(BasePlot.X_BOTTOM, Log10ScaleEngine())\n self.plot.setAxisScaleEngine(BasePlot.Y_LEFT, WavelengthScaleEngine(WAVELENGTHS))\n self.plot.setAxisScaleDraw(BasePlot.Y_LEFT, WavelengthScaleDraw(WAVELENGTHS))\n\nif __name__ == '__main__':\n app = QApplication(sys.argv)\n window = MultiHistogramPlot(rotate=False)\n \n # Create logarithmic bins and generate sample data\n log_bins = np.logspace(np.log10(LOG_MIN), np.log10(LOG_MAX), window.num_bins)\n sample_data = np.zeros((len(WAVELENGTHS), window.num_bins-1) if window.rotate else (window.num_bins-1, len(WAVELENGTHS)))\n \n # Generate histogram data for each wavelength\n for i in range(len(WAVELENGTHS)):\n # Generate raw data with multiple peaks\n num_samples = 100000\n mean1, std1 = 10**(2 + i*0.7), 10**(2 + i*0.7) * 0.3\n mean2, std2 = 10**(4 + i*0.5), 10**(4 + i*0.5) * 0.2\n \n # Create and combine samples\n raw_data = np.abs(np.concatenate([\n np.random.normal(mean1, std1, num_samples // 2),\n np.random.normal(mean2, std2, num_samples // 2)\n ]))\n \n # Create histogram with logarithmic bins\n hist_values, _ = np.histogram(raw_data, bins=log_bins)\n \n # Store histogram values in appropriate orientation\n if window.rotate:\n sample_data[i, :] = hist_values\n else:\n sample_data[:, i] = hist_values\n \n # Update the plot with the histogram data\n window.hist.set_data(sample_data)\n\n print(f\"After setting data: \")\n print(f\"self.image_param.xmin: {window.image_param.xmin}, \\\n self.image_param.xmax: {window.image_param.xmax}, \\\n self.image_param.ymin: {window.image_param.ymin}, \\\n self.image_param.ymax: {window.image_param.ymax}\")\n \n window.show()\n sys.exit(app.exec())\n```\n\n\nI've verified that these parameters are maintained after adding the item and setting data, but the visual result shows the Y-axis scaling is not being applied correctly.\n\n**_Expected Behavior_**\n\nThe `ImageItem` should stretch to fill the entire plot area defined by the axis limits, regardless of which axis uses the logarithmic scale.\n\n_**Actual Behavior**_\n\nThe `ImageItem` only stretches properly when the logarithmic scale is on the X-axis, but not when it's on the Y-axis.","author":{"url":"https://github.com/imad-miftek","@type":"Person","name":"imad-miftek"},"datePublished":"2025-03-08T20:11:55.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":4},"url":"https://github.com/36/PlotPy/issues/36"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:e61616a8-d75a-3588-7d03-5852c6899eb2 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | B494:209CC7:2CAD4CB:3D6DCC8:6972B4D3 |
| html-safe-nonce | 7c724d60436e6a2600b0da96280cadc1ebd6cbe9b0ecf903c8e031d1bc71a4ce |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJCNDk0OjIwOUNDNzoyQ0FENENCOjNENkRDQzg6Njk3MkI0RDMiLCJ2aXNpdG9yX2lkIjoiMzc1MjExNTUyNzg0NTMyODA4MyIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 596fdb301724a49b42f5f28ac4d88274f7b3747a8c4831182eefd630aeeb4f2a |
| hovercard-subject-tag | issue:2904996605 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/PlotPyStack/PlotPy/36/issue_layout |
| twitter:image | https://opengraph.githubassets.com/e7a1872b8828f1699ef64b80291f1beb1ce9c15258526332e087f656da014371/PlotPyStack/PlotPy/issues/36 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/e7a1872b8828f1699ef64b80291f1beb1ce9c15258526332e087f656da014371/PlotPyStack/PlotPy/issues/36 |
| og:image:alt | Problem Description I'm experiencing an issue where ImageItem properly stretches across the entire X-axis range but fails to occupy the full Y-axis range when using a logarithmic scale. This behavi... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | imad-miftek |
| hostname | github.com |
| expected-hostname | github.com |
| None | 51c0d0848f5569c6fa2198e9d69bd5f8f94a83c9fa3659e40728e7732afab130 |
| turbo-cache-control | no-preview |
| go-import | github.com/PlotPyStack/PlotPy git https://github.com/PlotPyStack/PlotPy.git |
| octolytics-dimension-user_id | 145201262 |
| octolytics-dimension-user_login | PlotPyStack |
| octolytics-dimension-repository_id | 671159499 |
| octolytics-dimension-repository_nwo | PlotPyStack/PlotPy |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 671159499 |
| octolytics-dimension-repository_network_root_nwo | PlotPyStack/PlotPy |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 10c5e2f2307495b2750073db87e9a5d3356a924f |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width