Skip to content

Conversation

@Imama-Kainat
Copy link
Contributor

@Imama-Kainat Imama-Kainat commented Mar 15, 2025

🔹 What This PR Fixes
This PR fixes the issue where log scaling was incorrectly applied to both PeakMap colors and marginal plots. Now:

Log scaling is applied only to self.z for PeakMap visualization. ✅
Marginal plots always display raw intensity values (self.z_original). ✅
Avoids unintended log scaling side effects on other properties. ✅
🔹 Changes Made
1️⃣ Stored original intensity values before applying log transformation (self.z_original).
2️⃣ Updated plot_marginals() to always use self.z_original (raw intensities).
3️⃣ Ensured log scaling applies only to self.z (color mapping), not marginal plots.

Summary by CodeRabbit

  • New Features

    • Introduced an optional log-scale transformation for the z-axis, offering clearer visualization of intensity values.
    • Enhanced plot displays now include marginal distribution views utilizing original intensity details.
    • Added configuration options for controlling log scaling of colors and intensities in peak map plots.
    • New test suite added to validate plotting functionalities, ensuring correct handling of log scaling and original intensity values.
  • Bug Fixes

    • Improved handling of marginal plots to ensure they utilize original intensity values even when log scaling is applied.

@coderabbitai
Copy link

coderabbitai bot commented Mar 15, 2025

Walkthrough

The changes refactor the PeakMapPlot class in the pyopenms_viz/_core.py module. A new constructor introduces an optional z_log_scale parameter, and a prepare_data method is added to manage intensity and z-value transformations, including applying logarithmic scaling. The plot method now integrates logic to include marginal distributions, calling a new method create_main_plot_marginals when needed. The PeakMapConfig class in pyopenms_viz/_config.py is updated with new attributes to control log scaling for colors and intensities. Additionally, modifications are made in the BOKEHPeakMapPlot and MATPLOTLIBPeakMapPlot classes to enhance plotting functionalities.

Changes

File Path Change Summary
pyopenms_viz/_core.py - Updated PeakMapPlot constructor to accept an optional z_log_scale parameter.
- Added prepare_data method for intensity conversion and z-value handling.
- Modified plot method to support plotting marginal distributions and use original intensity values when available.
- Added create_main_plot_marginals method.
pyopenms_viz/_config.py - Added attributes z_log_scale_colors (default True) and z_log_scale_intensity (default False) in PeakMapConfig.
- Updated __post_init__ for marginal plots and axis labels.
- Modified legend configuration for y-axis marginal plots.
pyopenms_viz/_bokeh/core.py - Changed create_main_plot method to a standalone function with enhanced functionality for PeakMap plotting.
- Added create_x_axis_plot and create_y_axis_plot methods.
pyopenms_viz/_matplotlib/core.py - Updated create_x_axis_plot, create_y_axis_plot, and create_main_plot methods to utilize z_original for marginal histograms and apply log scaling for color mapping.
test/peaktest.py - Introduced a new test suite with tests for the plot method and marginal plots, validating log scaling and original intensity values.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Plot as PeakMapPlot
    User->>Plot: Instantiate (optionally with z_log_scale=True)
    Note over Plot: Stores original intensity if z_log_scale is True
    User->>Plot: Call prepare_data()
    Plot->>Plot: Convert intensity to relative values & bin peaks\nApply log transformation on z if required
    User->>Plot: Call plot()
    Plot->>Plot: Ensure data is prepared\nCheck for 'add_marginals'
    alt Marginals Required
        Plot->>Plot: Call create_main_plot_marginals()
    end
Loading

Poem

Oh, how I hop with code so sweet,
Transforming plots with a rhythmic beat.
Logarithms and data neatly binned,
Marginal plots join the joyful spin.
A rabbit's cheer in every line we greet! 🐇✨

Tip

⚡🧪 Multi-step agentic review comment chat (experimental)
  • We're introducing multi-step agentic chat in review comments. This experimental feature enhances review discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments.
    - To enable this feature, set early_access to true under in the settings.
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@Imama-Kainat
Copy link
Contributor Author

@maintainers I haven’t completed 100% testing yet, so please hold off on reviewing it for now.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40549e8 and 2b0ee07.

📒 Files selected for processing (1)
  • pyopenms_viz/_core.py (2 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
pyopenms_viz/_core.py

40-40: Undefined name BaseMSPlot

(F821)


138-138: Undefined name plot_marginal_histogram

(F821)

🪛 GitHub Actions: continuous-integration
pyopenms_viz/_core.py

[error] 40-40: NameError: name 'BaseMSPlot' is not defined

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
pyopenms_viz/_config.py (2)

451-452: Remove duplicate code setting z_log_scale for marginal plots.

The settings for ensuring marginals use raw intensity values appear twice - once at lines 451-452 and again at lines 483-484. This duplication should be removed to avoid potential maintenance issues.

# Keep lines 451-452
self.y_plot_config.z_log_scale = False
self.x_plot_config.z_log_scale = False

# Remove lines 483-484
-# Ensure marginals always use raw intensity values
-self.y_plot_config.z_log_scale = False
-self.x_plot_config.z_log_scale = False

Also applies to: 483-484


474-479: Remove duplicate code for z-setting and annotation data.

This is another instance of duplicate code between your new implementation and the existing code. The same logic for handling when fill_by_z is False and copying annotation data appears in both places.

# Keep lines 474-479

# Remove lines 515-521
-# If we do not want to fill/color based on z value, set to none prior to plotting
-if not self.fill_by_z:
-    self.z = None
-
-# create copy of annotation data
-self.annotation_data = (
-    None if self.annotation_data is None else self.annotation_data.copy()
-)

Also applies to: 515-521

pyopenms_viz/_core.py (2)

39-40: Remove unused imports.

The imports for IS_SPHINX_BUILD, IS_NOTEBOOK, and warnings are not used in this file.

-from .constants import IS_SPHINX_BUILD, IS_NOTEBOOK
-import warnings
+# Remove unused imports
🧰 Tools
🪛 Ruff (0.8.2)

39-39: .constants.IS_SPHINX_BUILD imported but unused

Remove unused import

(F401)


39-39: .constants.IS_NOTEBOOK imported but unused

Remove unused import

(F401)


40-40: warnings imported but unused

Remove unused import: warnings

(F401)


149-149: Add type annotation for 'figure'.

The return type "figure" is used but not defined or imported, which could cause confusion or type checking issues.

-def create_x_axis_plot(self, canvas=None) -> "figure":
+def create_x_axis_plot(self, canvas=None) -> Any:  # Or import the specific figure type

Alternatively, you could import the appropriate figure type from your plotting backend (matplotlib, bokeh, etc.).

🧰 Tools
🪛 Ruff (0.8.2)

149-149: Undefined name figure

(F821)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b0ee07 and 4f23446.

📒 Files selected for processing (2)
  • pyopenms_viz/_config.py (2 hunks)
  • pyopenms_viz/_core.py (4 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
pyopenms_viz/_core.py

39-39: .constants.IS_SPHINX_BUILD imported but unused

Remove unused import

(F401)


39-39: .constants.IS_NOTEBOOK imported but unused

Remove unused import

(F401)


40-40: warnings imported but unused

Remove unused import: warnings

(F401)


149-149: Undefined name figure

(F821)

🔇 Additional comments (5)
pyopenms_viz/_config.py (1)

415-417: Good addition of log scaling control attributes.

The new attributes z_log_scale_colors and z_log_scale_intensity provide a clear way to control log scaling behavior, ensuring colors are log-scaled but raw intensities are preserved.

pyopenms_viz/_core.py (4)

5-5: Good job addressing import issues.

Nice work fixing the import issues mentioned in previous reviews for BaseMSPlot and plot_marginal_histogram.

Also applies to: 36-36


53-70: Well-implemented constructor with intensity preservation logic.

The constructor properly combines the previously separate __init__ methods and correctly handles log scaling. Good defensive programming by storing self.z_original regardless of whether log scaling is applied.


94-113: Good data preparation implementation.

The prepare_data method correctly handles data preparation including validation, relative intensity conversion, log scaling, and data sorting. The validation check for self.z is a good defensive programming practice.


78-90:

❓ Verification inconclusive

Verify that plot_marginals() is called appropriately.

The plot_marginals() method is well-implemented to ensure raw intensity values are used in marginal plots. However, it's unclear if this method is being properly called from the plot() method or elsewhere.

Run this script to verify if plot_marginals() is called within the codebase:


🏁 Script executed:

#!/bin/bash
# Search for calls to plot_marginals in the codebase
rg -A 1 -B 1 "plot_marginals\(\)"

Length of output: 236


ACTION: Verify Integration of plot_marginals() in the Plotting Workflow

Our investigation indicates that while plot_marginals() is correctly implemented to ensure raw intensity values are used in the marginal plots, a direct call to this method does not appear in the codebase. The search results only show calls to methods like create_main_plot_marginals() and create_x_axis_plot(), with no explicit invocation of plot_marginals().

  • Confirm whether plot_marginals() is intended to be called directly (or indirectly within another method such as create_main_plot_marginals()).
  • If the marginal plotting functionality relies on an explicit call to plot_marginals(), please integrate that call within the main plotting workflow.
  • Otherwise, if this method is now redundant or its functionality is fully encapsulated by other methods, consider removing or refactoring it to avoid confusion.

Comment on lines +455 to +471
self.y_plot_config.xlabel = self.zlabel
self.y_plot_config.ylabel = self.ylabel
self.x_plot_config.ylabel = self.zlabel
self.y_plot_config.y_axis_location = "left"
self.x_plot_config.y_axis_location = "right"

# ✅ Update default settings for better visualization
self.y_plot_config.legend_config.show = True
self.y_plot_config.legend_config.loc = "below"
self.y_plot_config.legend_config.orientation = "horizontal"
self.y_plot_config.legend_config.bbox_to_anchor = (1, -0.4)

# ✅ Remove titles from marginal plots
if self.add_marginals:
self.title = ""
self.x_plot_config.title = ""
self.y_plot_config.title = ""
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Eliminate duplicate configuration code.

There's significant duplication between the new section (lines 455-471) and the existing code below (lines 487-512). Both blocks update the same configuration settings. This duplication creates maintenance challenges and potential inconsistencies.

Remove the redundant code by keeping only one set of these configurations:

  • Label updating
  • Legend configuration
  • Title removal
  • Positioning settings
# Keep lines 455-471 with all the settings

# Remove redundant lines 487-512
-# update y-axis labels and positioning to defaults
-self.y_plot_config.xlabel = self.zlabel
-self.y_plot_config.ylabel = self.ylabel
-self.y_plot_config.y_axis_location = "left"
-self.y_plot_config.legend_config.show = True
-self.y_plot_config.legend_config.loc = "below"
-self.y_plot_config.legend_config.orientation = "horizontal"
-self.y_plot_config.legend_config.bbox_to_anchor = (1, -0.4)
-self.y_plot_config.min_border = 0
-self.y_plot_config.height = self.height
-if self.y_kind == "spectrum":
-    self.y_plot_config.direction = "horizontal"

-# update x-axis labels and positioning to defaults
-self.x_plot_config.ylabel = self.zlabel
-self.x_plot_config.y_axis_location = "right"
-self.x_plot_config.legend_config.show = True
-self.x_plot_config.legend_config.loc = "right"
-self.x_plot_config.width = self.width
-self.x_plot_config.min_border = 0

-# remove titles if marginal plot
-if self.add_marginals:
-    self.title = ""
-    self.x_plot_config.title = ""
-    self.y_plot_config.title = ""

Make sure to add back any critical settings from the removed section that aren't in the new code, like:

# Add these lines after line 458
+self.y_plot_config.min_border = 0
+self.y_plot_config.height = self.height
+if self.y_kind == "spectrum":
+    self.y_plot_config.direction = "horizontal"
+self.x_plot_config.width = self.width
+self.x_plot_config.min_border = 0

Also applies to: 487-512

@jcharkow jcharkow requested a review from singjc March 15, 2025 13:43
@jcharkow
Copy link
Collaborator

Have not done a complete review yet however, it seems that we are getting some failed tests. I expect some to fail since you are updating the code however I am concerned with the documentation not building. Can you please look into this?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f23446 and c8374a9.

📒 Files selected for processing (5)
  • app.py (1 hunks)
  • pyopenms_viz/_bokeh/core.py (1 hunks)
  • pyopenms_viz/_core.py (2 hunks)
  • pyopenms_viz/_matplotlib/core.py (5 hunks)
  • test/peaktest.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • app.py
🧰 Additional context used
🪛 Ruff (0.8.2)
pyopenms_viz/_bokeh/core.py

563-563: bokeh.plotting.figure imported but unused

Remove unused import: bokeh.plotting.figure

(F401)


564-564: bokeh.models.ColorBar imported but unused

Remove unused import

(F401)


564-564: bokeh.models.LinearColorMapper imported but unused

Remove unused import

(F401)


567-567: Local variable log_intensity is assigned to but never used

Remove assignment to unused variable log_intensity

(F841)


567-567: Undefined name np

(F821)

🔇 Additional comments (11)
test/peaktest.py (2)

6-30: Well structured parametrized tests for PeakMap functionality.

The test suite effectively validates different configurations of the plot method, particularly testing the log scaling feature. It's good to see explicit assertions confirming that log-scaled intensities have no negative values.


33-41: Good test for marginal plots using raw intensities.

This test properly verifies that marginal plots use raw intensity values even when log scaling is applied to the main plot. The assertion for z_original attribute ensures the implementation behaves as expected.

pyopenms_viz/_bokeh/core.py (3)

594-602: Raw intensity values correctly used in X-axis marginal histogram.

The implementation properly checks for the presence of z_original column and uses it for the histogram, ensuring that raw intensity values are displayed.


607-622: Raw intensity values correctly used in Y-axis marginal histogram.

Similar to the X-axis plot, this implementation properly checks for the presence of z_original column and uses it for the histogram. This ensures consistency across both marginal plots.


588-602: ⚠️ Potential issue

Missing numpy import.

The code uses np.log1p() but the numpy module is referred to as np without an import statement.

+import numpy as np

Likely an incorrect or invalid review comment.

pyopenms_viz/_core.py (3)

1121-1128: Good implementation of z_log_scale parameter and original intensity preservation.

The constructor correctly initializes the z_log_scale parameter and preserves the original intensity values in z_original before any transformations are applied. This is essential for maintaining the raw data for marginal plots.


1134-1147: Well-documented data preparation method.

The prepare_data method now properly saves the original intensity values and applies log scaling only to the color_intensity column when requested. The docstring clearly explains the purpose of the method.


1183-1204: Clear implementation of marginal plots with raw intensities.

The create_main_plot_marginals method ensures that marginal plots use raw intensities from z_original even when log scaling is applied to the main plot. The implementation is well-documented and configures the subplot space appropriately.

pyopenms_viz/_matplotlib/core.py (3)

632-654: Well-documented X-axis marginal plot with raw intensity values.

The create_x_axis_plot method properly checks for and uses z_original data for the histogram, ensuring raw intensity values are displayed. Good docstring and explanatory comments.


657-698: Y-axis marginal plot correctly uses raw intensity data.

The implementation properly selects z_original when available for the marginal plots. Clear documentation and comments explain the purpose of the changes.


699-734: Main plot correctly applies log scaling only to colors.

The create_main_plot method now correctly applies log scaling only to the color mapping using log_intensity, while preserving raw data for other purposes. The implementation is well-documented with clear comments.

Comment on lines +557 to +582
def create_main_plot(self, canvas=None):
"""
Implements PeakMap plotting for Bokeh.
- Applies log scaling only to color mapping.
- Uses raw intensities for marginal histograms.
"""
from bokeh.plotting import figure
from bokeh.models import ColorBar, LinearColorMapper

if not self.plot_3d:
# ✅ Apply log scaling **only for PeakMap color scale**
log_intensity = np.log1p(self.data[self.z]) if self.z_log_scale else self.data[self.z]

scatterPlot = self.get_scatter_renderer(data=self.data, config=self._config)
scatterPlot = self.get_scatter_renderer(data=self.data, config=self._config)

tooltips, custom_hover_data = self._create_tooltips(
{self.xlabel: self.x, self.ylabel: self.y, "intensity": self.z}
)
tooltips, custom_hover_data = self._create_tooltips(
{self.xlabel: self.x, self.ylabel: self.y, "intensity": self.z}
)

fig = scatterPlot.generate(tooltips, custom_hover_data)
self.main_fig = fig # Save the main figure for later use
# ✅ Use log-transformed intensity only for color mapping
fig = scatterPlot.generate(tooltips, custom_hover_data)
self.main_fig = fig # Save the main figure for later use

if self.annotation_data is not None:
self._add_box_boundaries(self.annotation_data)
if self.annotation_data is not None:
self._add_box_boundaries(self.annotation_data)

else:
raise NotImplementedError("3D PeakMap plots are not supported in Bokeh")
return fig
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Log scaling implementation correctly applied only to color mapping.

The create_main_plot function now correctly applies log scaling selectively. However, there are some issues with unused imports and variables that should be addressed.

-from bokeh.plotting import figure
-from bokeh.models import ColorBar, LinearColorMapper
+# Remove unused imports

-# ✅ Apply log scaling **only for PeakMap color scale**
-log_intensity = np.log1p(self.data[self.z]) if self.z_log_scale else self.data[self.z]
+# ✅ Apply log scaling **only for PeakMap color scale**
+# Use the transformed data directly where needed instead of creating an unused variable

The log_intensity variable is calculated but never used. The transformation should be applied directly where needed or the variable should be used in the subsequent plotting code.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def create_main_plot(self, canvas=None):
"""
Implements PeakMap plotting for Bokeh.
- Applies log scaling only to color mapping.
- Uses raw intensities for marginal histograms.
"""
from bokeh.plotting import figure
from bokeh.models import ColorBar, LinearColorMapper
if not self.plot_3d:
# ✅ Apply log scaling **only for PeakMap color scale**
log_intensity = np.log1p(self.data[self.z]) if self.z_log_scale else self.data[self.z]
scatterPlot = self.get_scatter_renderer(data=self.data, config=self._config)
scatterPlot = self.get_scatter_renderer(data=self.data, config=self._config)
tooltips, custom_hover_data = self._create_tooltips(
{self.xlabel: self.x, self.ylabel: self.y, "intensity": self.z}
)
tooltips, custom_hover_data = self._create_tooltips(
{self.xlabel: self.x, self.ylabel: self.y, "intensity": self.z}
)
fig = scatterPlot.generate(tooltips, custom_hover_data)
self.main_fig = fig # Save the main figure for later use
# ✅ Use log-transformed intensity only for color mapping
fig = scatterPlot.generate(tooltips, custom_hover_data)
self.main_fig = fig # Save the main figure for later use
if self.annotation_data is not None:
self._add_box_boundaries(self.annotation_data)
if self.annotation_data is not None:
self._add_box_boundaries(self.annotation_data)
else:
raise NotImplementedError("3D PeakMap plots are not supported in Bokeh")
return fig
def create_main_plot(self, canvas=None):
"""
Implements PeakMap plotting for Bokeh.
- Applies log scaling only to color mapping.
- Uses raw intensities for marginal histograms.
"""
# Removed unused imports
# ✅ Apply log scaling **only for PeakMap color scale**
# Use the transformed data directly where needed instead of creating an unused variable
scatterPlot = self.get_scatter_renderer(data=self.data, config=self._config)
tooltips, custom_hover_data = self._create_tooltips(
{self.xlabel: self.x, self.ylabel: self.y, "intensity": self.z}
)
# ✅ Use log-transformed intensity only for color mapping
fig = scatterPlot.generate(tooltips, custom_hover_data)
self.main_fig = fig # Save the main figure for later use
if self.annotation_data is not None:
self._add_box_boundaries(self.annotation_data)
return fig
🧰 Tools
🪛 Ruff (0.8.2)

563-563: bokeh.plotting.figure imported but unused

Remove unused import: bokeh.plotting.figure

(F401)


564-564: bokeh.models.ColorBar imported but unused

Remove unused import

(F401)


564-564: bokeh.models.LinearColorMapper imported but unused

Remove unused import

(F401)


567-567: Local variable log_intensity is assigned to but never used

Remove assignment to unused variable log_intensity

(F841)


567-567: Undefined name np

(F821)

@Imama-Kainat
Copy link
Contributor Author

Hey @t0mdavid-m @jcharkow ,

I have made the following improvements to the PeakMap plotting logic:

✅ Applied log scaling only to color mapping** (z values) to prevent unintended intensity distortions.
✅ Ensured marginal histograms (x and y) use raw intensity (z_original)** for correct visualization.
✅ Adjusted axis ranges in create_x_axis_plot() and create_y_axis_plot()** to maintain proper alignment with the main PeakMap plot.

Please review and let me know if any modifications are needed.

Thanks!

@Imama-Kainat
Copy link
Contributor Author

Summary of Changes & Fixes

🔹 Log Scaling Applied Only to Colors

Previously, log scaling affected the entire dataset, distorting histograms.
Fix: Log transformation (np.log1p) is now only applied to color mapping (z), keeping raw values intact.
🔹 Marginal Histograms Use z_original

X and Y histograms should display raw intensity values, not log-transformed ones.
Fix: Used z_original for histograms, ensuring correct intensity representation.

@jcharkow
Copy link
Collaborator

@Imama-Kainat can you please link to the issue this is solving (e.g. edit the description and add the issue that this addresses)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
pyopenms_viz/_core.py (3)

1121-1122: Remove redundant pandas import.

pandas is already imported at line 10 with specific components, making this import unnecessary.

-    import pandas as pd

1123-1135: Good implementation for preserving original intensity values.

You've correctly added the z_log_scale parameter and implemented proper logic to store original intensity values before transformation. The error handling for various edge cases is thorough.

However, note the inconsistency with how original values are stored: here you set self.z_original, but in prepare_data() you use data["z_original"].

Consider using a consistent approach to store original values - either as an instance attribute or as a dataframe column, not both.

🧰 Tools
🪛 Ruff (0.8.2)

1129-1129: Undefined name pd

(F821)


1191-1212: Excellent fix for marginal plots using raw intensities.

This implementation ensures that marginal histograms display raw intensity values instead of log-transformed ones, which addresses the core issue described in the PR objectives.

The method properly:

  1. Calls the abstract create_main_plot() function
  2. Adds histograms that use z_original for intensity values
  3. Keeps subplot spacing and layout consistent

Some matplotlib-specific code is embedded here, which might be better in the matplotlib implementation.

Consider whether this matplotlib-specific implementation should be moved to the matplotlib backend class.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c8374a9 and a6fe9c5.

📒 Files selected for processing (1)
  • pyopenms_viz/_core.py (2 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
pyopenms_viz/_core.py

1129-1129: Undefined name pd

(F821)

🔇 Additional comments (1)
pyopenms_viz/_core.py (1)

1141-1155: Well-structured data preparation logic.

The prepare_data method clearly separates the concerns by:

  1. Preserving original intensity values
  2. Only applying log scaling to color mapping
  3. Using descriptive variable names for clarity

This implementation correctly addresses the issue where log scaling was previously applied to both colors and marginal plots.

@Imama-Kainat Imama-Kainat changed the title Ensure PeakMap log scaling applies only to colors, keeping marginal plots raw Ensure PeakMap log scaling applies only to colors, keeping marginal plots raw issue 65 Mar 15, 2025
@Imama-Kainat
Copy link
Contributor Author

image
my log scaling solution flow

@timosachsenberg
Copy link

  • can you resolve merge issues
  • can you post several example images here?

Copy link
Collaborator

@singjc singjc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, this is a good first attempt, but there are a few issues.

  • There are some additional variables you create, which don't actually seem to get used anywhere. i.e (log_intensity, self.data["color_intensity"])
  • You add additional histogram marginal plots, not sure why?
  • I'm not sure what the purpose of the added circle glyphs for bokeh are for?
  • We probably don't want to remove the option of plotting relative intensity or perform binning on the data.
  • We would want to also ensure the same logic gets added/applied to the plotly backend.

Overall, there just seems to be a lot of code changes, where I'm not sure what the purpose of the changes are, and why we're removing some parts that seem necessary.

I was originally thinking all we would need to change is the z value data used in pyopenms_viz/_core.py#L1229 and pyopenms_viz/_core.py#L1264. This would only require changes to the _core.py file, and wouldn't require changes to the individual backends. I would maybe suggest starting a fresh PR with this implementation idea (since this one seem so to have a lot of changes).

Comment on lines +557 to +582
def create_main_plot(self, canvas=None):
"""
Implements PeakMap plotting for Bokeh.
- Applies log scaling only to color mapping.
- Uses raw intensities for marginal histograms.
"""
from bokeh.plotting import figure
from bokeh.models import ColorBar, LinearColorMapper

if not self.plot_3d:
# ✅ Apply log scaling **only for PeakMap color scale**
log_intensity = np.log1p(self.data[self.z]) if self.z_log_scale else self.data[self.z]

scatterPlot = self.get_scatter_renderer(data=self.data, config=self._config)
scatterPlot = self.get_scatter_renderer(data=self.data, config=self._config)

tooltips, custom_hover_data = self._create_tooltips(
{self.xlabel: self.x, self.ylabel: self.y, "intensity": self.z}
)
tooltips, custom_hover_data = self._create_tooltips(
{self.xlabel: self.x, self.ylabel: self.y, "intensity": self.z}
)

fig = scatterPlot.generate(tooltips, custom_hover_data)
self.main_fig = fig # Save the main figure for later use
# ✅ Use log-transformed intensity only for color mapping
fig = scatterPlot.generate(tooltips, custom_hover_data)
self.main_fig = fig # Save the main figure for later use

if self.annotation_data is not None:
self._add_box_boundaries(self.annotation_data)
if self.annotation_data is not None:
self._add_box_boundaries(self.annotation_data)

else:
raise NotImplementedError("3D PeakMap plots are not supported in Bokeh")
return fig
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the indentation of this method.


return fig


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove extra space

Comment on lines +560 to +561
- Applies log scaling only to color mapping.
- Uses raw intensities for marginal histograms.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you put these under a note

Suggested change
- Applies log scaling only to color mapping.
- Uses raw intensities for marginal histograms.
Notes
-----
Applies log scaling only for the color scale of the PeakMap, and uses the raw values (i.e. intensities) for marginal plots (i.e. spectrum, chromatogram, mobilogram).

from bokeh.models import ColorBar, LinearColorMapper

if not self.plot_3d:
# ✅ Apply log scaling **only for PeakMap color scale**
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove this comment

Suggested change
# ✅ Apply log scaling **only for PeakMap color scale**


fig = scatterPlot.generate(tooltips, custom_hover_data)
self.main_fig = fig # Save the main figure for later use
# ✅ Use log-transformed intensity only for color mapping
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove this comment

Suggested change
# ✅ Use log-transformed intensity only for color mapping

# ✅ Apply log scaling **only for PeakMap colors**.
if self.z_log_scale:
self.data[self.z] = log1p(self.data[self.z])
self.data["color_intensity"] = np.log1p(self.data[self.z])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does self.data["color_intensity"] even get used anywhere in the backends plotting logic?

Comment on lines -1166 to -1168
# Sort values by intensity in ascending order to plot highest intensity peaks last
if self.z is not None:
self.data = self.data.sort_values(self.z)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't sort the intensities, then you will get plots where the high intensities will get masked/covered by other intensities, which doesn't result in a nice visualization.

Comment on lines +1191 to +1211
def create_main_plot_marginals(self, canvas=None):
"""
Calls the abstract create_main_plot() function but ensures:
- Log scaling is only applied to PeakMap colors.
- Marginal plots use raw intensities (`z_original`).
"""
fig = self.create_main_plot(canvas)

if self.add_marginals:
fig.subplots_adjust(hspace=0.2, wspace=0.2)
grid = fig.add_gridspec(4, 4)

ax_marg_x = fig.add_subplot(grid[0, 0:3], sharex=fig.axes[0])
ax_marg_x.hist(self.data["z_original"], bins=30, color="gray", alpha=0.6)
ax_marg_x.axis("off")

ax_marg_y = fig.add_subplot(grid[1:4, 3], sharey=fig.axes[0])
ax_marg_y.hist(self.data["z_original"], bins=30, orientation="horizontal", color="gray", alpha=0.6)
ax_marg_y.axis("off")

return fig
Copy link
Collaborator

@singjc singjc Mar 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be indented, but also why is there added code here for adding the marginals? This is handled by the main plot method? pyopenms_viz/_core.py#L1159-L1169

The marginals also should not be histograms, they should be of kind chromatogram, spectrum or mobilogram.


# ✅ Ensure raw intensity values are used for the marginal histogram
if "z_original" in self.data.columns:
ax.hist(self.data["z_original"], bins=30, color="gray", alpha=0.6)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we adding a histogram here to the x-axis marginal plot?

x=self.x,
y=self.y,
z=self.z,
z=log_intensity, # ✅ Only log-transform color scale
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think z is expected to be a string for the column variable, not a numpy array.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants