Skip to content

Interface for data operations - #101

Draft
mfiaudrin-epsyl wants to merge 15 commits into
iterorganization:developfrom
mfiaudrin-epsyl:feature/data_operations
Draft

Interface for data operations#101
mfiaudrin-epsyl wants to merge 15 commits into
iterorganization:developfrom
mfiaudrin-epsyl:feature/data_operations

Conversation

@mfiaudrin-epsyl

@mfiaudrin-epsyl mfiaudrin-epsyl commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

This PR implements the interface that allows you to perform two types of operations on data:

  • smoothing
  • unary operations

This PR also closes #104 by preserving transposition after a data manipulations (downsampling, interpolation, smoothing and unary operations)

You can test this feature in the data manipulation component. Simply open the corresponding accordion (data smoothing or unary operations) and give it a try:
image

Reminder
I need you to approve the PR #90 in order to implement a common interface that lets you choose between unary and binary operations.

@prasad-sawantdesai prasad-sawantdesai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are we also suppose to support operations on signals (like comparing two signals)
image

@olivhoenen

olivhoenen commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Are we also suppose to support operations on signals (like comparing two signals)

Yes, this is the binary operations mentioned by @mfiaudrin-epsyl and depending on #90 being merged

@prasad-sawantdesai

prasad-sawantdesai commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Should we guard sigma input (not less than or equal to "0")?

image
INFO:     127.0.0.1:45640 - "GET /data/plot_data?uri=imas%3Ahdf5%3Fpath%3D%2Fwork%2Fimas%2Fshared%2Fimasdb%2FITER%2F3%2F100120%2F1%23summary%3A0%2Fglobal_quantities%2Fip%2Fvalue&interpolation_method=exact_value&smoothing_method=gaussian_filter&gaussian_smoothing_sigma=0 HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/ITER/sawantp1/.local/lib/python3.11/site-packages/uvicorn/protocols/http/httptools_impl.py", line 421, in run_asgi
.
               ^^^^^^^^^^^^^^^^^^^^^
  File "/home/ITER/sawantp1/github/IBEX/backend/ibex/endpoints/data.py", line 120, in plot_data
    return CustomORJSONResponse(ibex_service.get_plot_data(plot_data_query))
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ITER/sawantp1/github/IBEX/backend/ibex/core/ibex_service.py", line 121, in get_plot_data
    return data_source.get_plot_data(plot_data_query)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ITER/sawantp1/github/IBEX/backend/ibex/data_source/imas_python_source.py", line 1103, in get_plot_data
    data_to_be_returned = apply_gaussian_filter(
                          ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ITER/sawantp1/github/IBEX/backend/ibex/data_source/imas_python_source_utils.py", line 178, in apply_gaussian_filter
    return gaussian_filter1d(data, sigma=sigma)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ITER/sawantp1/.local/lib/python3.11/site-packages/scipy/ndimage/_filters.py", line 752, in gaussian_filter1d
    weights = _gaussian_kernel1d(sigma, order, lw)[::-1]
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ITER/sawantp1/.local/lib/python3.11/site-packages/scipy/ndimage/_filters.py", line 665, in _gaussian_kernel1d
    phi_x = np.exp(-0.5 / sigma2 * x ** 2)
                   ~~~~~^~~~~~~~
ZeroDivisionError: float division by zero

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like changing data smoothing of one signal applies to both. Is it correct behavior?
Original

Image

Applied downsampling and data smoothing

Image

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Hello @prasad-sawantdesai,

The behavior you mention is unexpected. In the last version of my branch feature/data_operations, the data smoothing is only applied to the selected plot.

Can you please confirm if your behavior is occuring in the updated feature/data_operations branch ? If it is, I will need your configuration to reproduce it.

@olivhoenen olivhoenen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fix the import duplication (from a wrong conflict resolve)

Comment thread frontend/src/renderer/components/grid/HoverButtons.tsx Outdated
@mfiaudrin-epsyl

Copy link
Copy Markdown
Collaborator Author

Should we guard sigma input (not less than or equal to "0")?

image ```shell INFO: 127.0.0.1:45640 - "GET /data/plot_data?uri=imas%3Ahdf5%3Fpath%3D%2Fwork%2Fimas%2Fshared%2Fimasdb%2FITER%2F3%2F100120%2F1%23summary%3A0%2Fglobal_quantities%2Fip%2Fvalue&interpolation_method=exact_value&smoothing_method=gaussian_filter&gaussian_smoothing_sigma=0 HTTP/1.1" 500 Internal Server Error ERROR: Exception in ASGI application Traceback (most recent call last): File "/home/ITER/sawantp1/.local/lib/python3.11/site-packages/uvicorn/protocols/http/httptools_impl.py", line 421, in run_asgi . ^^^^^^^^^^^^^^^^^^^^^ File "/home/ITER/sawantp1/github/IBEX/backend/ibex/endpoints/data.py", line 120, in plot_data return CustomORJSONResponse(ibex_service.get_plot_data(plot_data_query)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/ITER/sawantp1/github/IBEX/backend/ibex/core/ibex_service.py", line 121, in get_plot_data return data_source.get_plot_data(plot_data_query) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/ITER/sawantp1/github/IBEX/backend/ibex/data_source/imas_python_source.py", line 1103, in get_plot_data data_to_be_returned = apply_gaussian_filter( ^^^^^^^^^^^^^^^^^^^^^^ File "/home/ITER/sawantp1/github/IBEX/backend/ibex/data_source/imas_python_source_utils.py", line 178, in apply_gaussian_filter return gaussian_filter1d(data, sigma=sigma) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/ITER/sawantp1/.local/lib/python3.11/site-packages/scipy/ndimage/_filters.py", line 752, in gaussian_filter1d weights = _gaussian_kernel1d(sigma, order, lw)[::-1] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/ITER/sawantp1/.local/lib/python3.11/site-packages/scipy/ndimage/_filters.py", line 665, in _gaussian_kernel1d phi_x = np.exp(-0.5 / sigma2 * x ** 2) ~~~~~^~~~~~~~ ZeroDivisionError: float division by zero ```

I can prevent from having 0 in sigma field because it's a quick improvement. I will it do it when I will have missing endpoints from the BE.

@olivhoenen

Copy link
Copy Markdown
Contributor

I can prevent from having 0 in sigma field because it's a quick improvement. I will it do it when I will have missing endpoints from the BE.

No need for the time being, as mentioned in #101 (comment)
What we need is the binary operator in this PR, I believe the corresponding backend PR was merged already.

@mfiaudrin-epsyl
mfiaudrin-epsyl marked this pull request as draft July 31, 2026 06:55
@mfiaudrin-epsyl

Copy link
Copy Markdown
Collaborator Author

Hello @olivhoenen ,

Following the toolchain version update, I need PR #116 to be approved and merged into the “develop” branch.

That way, I can run the application on that branch to implement the binary operations.

@olivhoenen

Copy link
Copy Markdown
Contributor

Hello @olivhoenen ,

Following the toolchain version update, I need PR #116 to be approved and merged into the “develop” branch.

That way, I can run the application on that branch to implement the binary operations.

Ready now for you

@mfiaudrin-epsyl

Copy link
Copy Markdown
Collaborator Author

Hello @olivhoenen ,
Following the toolchain version update, I need PR #116 to be approved and merged into the “develop” branch.
That way, I can run the application on that branch to implement the binary operations.

Ready now for you

Thank you I am on it

@mfiaudrin-epsyl
mfiaudrin-epsyl marked this pull request as ready for review August 4, 2026 06:42
@mfiaudrin-epsyl

Copy link
Copy Markdown
Collaborator Author

@olivhoenen, I just implement the operations on signals.

The data operation has been finalized. Now we can choose between unary or binary operations.

The PR is ready for review.

@mfiaudrin-epsyl

Copy link
Copy Markdown
Collaborator Author

@prasad-sawantdesai , @olivhoenen,
FYI now we guard sigma input. The minimum allowed is "0.1".

@olivhoenen

olivhoenen commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Thanks @mfiaudrin-epsyl, concentrating here on data operations as the other data manipulations were tested before and looked fine to me.

There are a few issues/questions on the data operations implementation here, tested from this config
test-signal-proc2IbexState.json :

  1. why this limitation of always applying constant operations before signal ones?
image
  1. I thought that the backend was now returning updated units when dealing with signal operations, it seems not to be the case in the frontend, so either the backend still does not do it properly (after ad0a37b) or the frontend does not take this info into account

  2. we need to be able to give a different name to the resulting signal, A op B = C (currenly we end up in A op B = A), but this may be related to the choice of UI with selection of A via the tabs? Any reason why the data operation always replaces one operand signal rather than give an additional one (which would be logical once point 2 is corrected as it may be of a different unit)

  3. speaking of the choice of tabs to select the first operand A, it took me a while and several tries to understand this logic, the UI is not very user friendly in such a case (while I understand it works ok for smoothing, interpolation and downsampling). At minima there shall be a short explanation of what is A and B in data operations of kind "signal", and change the description of signal from "Plot to combine with" (which I find unclear) with "Select the operand"

image

@olivhoenen olivhoenen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see a regression in this PR, inside the metadata menu with the config test-signal-proc2IbexState(1).json:
when I click on tabs for URI-1 or URI-2, it returns automatically to URI-0
Image

This is not happening with the same config on develop.

@mfiaudrin-epsyl

Copy link
Copy Markdown
Collaborator Author

I see a regression in this PR, inside the metadata menu with the config test-signal-proc2IbexState(1).json: when I click on tabs for URI-1 or URI-2, it returns automatically to URI-0 Image

This is not happening with the same config on develop.

Thank you for your feedbacks. About the regression you have seen, It's a bug that we didn't reproduce before because it's also in develop. It's occuring when we have a graph in edition mode. In this case, we have the unexpected behavior. After some tests, I confirm that the bug is occuring only in metadata component.

I will let you know when it'll be fixed.

@mfiaudrin-epsyl
mfiaudrin-epsyl marked this pull request as draft August 5, 2026 13:08
@mfiaudrin-epsyl

Copy link
Copy Markdown
Collaborator Author

Thanks @mfiaudrin-epsyl, concentrating here on data operations as the other data manipulations were tested before and looked fine to me.

There are a few issues/questions on the data operations implementation here, tested from this config test-signal-proc2IbexState.json :

  1. why this limitation of always applying constant operations before signal ones?
image 2. I thought that the backend was now returning updated units when dealing with signal operations, it seems not to be the case in the frontend, so either the backend still does not do it properly (after [ad0a37b](https://github.com/iterorganization/IBEX/commit/ad0a37bc908aed22417349c122b198a879622993)) or the frontend does not take this info into account 3. we need to be able to give a different name to the resulting signal, `A op B = C` (currenly we end up in `A op B = A`), but this may be related to the choice of UI with selection of `A` via the tabs? Any reason why the data operation always replaces one operand signal rather than give an additional one (which would be logical once point 2 is corrected as it may be of a different unit) 4. speaking of the choice of tabs to select the first operand `A`, it took me a while and several tries to understand this logic, the UI is not very user friendly in such a case (while I understand it works ok for smoothing, interpolation and downsampling). At minima there shall be a short explanation of what is A and B in data operations of kind "signal", and change the description of signal from "Plot to combine with" (which I find unclear) with "Select the operand" image

Thanks for the detailed review. Answers point by point:

  1. Why constants are always applied before signal operations

This isn't a UI choice, it's a backend constraint. The two features (unary and binary operations) travel through two distinct query parameters (operations and signal_operations).

So the request order can't be honoured whatever the frontend sends. Lifting this would require a backend change: a single ordered parameter where each entry is either type:value or type:uri, applied in request order. @jwasikpsnc Do you confirm it?

I don't know if it it's doable in this scope or if it's a feature. I'd suggest tracking that as a separate backend issue rather than folding it into this PR. In the meantime the UI only shows that warning when both kinds are actually mixed.

  1. Units

You're right, and it's on the frontend side. The backend is fine, it does return the updated unit in the response. The frontend stores it on the plot, but the unit displayed in the axis title and in the metadata panel is read from the axis (yAxisData.unit), which I never update. I'll fix that.

However, a new unit coming out of an operation causes a problem, because we currently have a hard limit of 2 Y axes, hence 2 different units per grid. To display the result of an operation, that constraint has to go.

This is doable: plotly.js supports more than 2 Y axes, and the version we bundle handles the positioning of the extra axes automatically. The trade-off is readability: a grid with many Y axes becomes harder to read, and each extra axis eats into the plot area.

Here is what it would look like:
image

As a side benefit, this also lifts the limitation when adding signals from the tree: users would no longer be capped at 2 different units.

One thing to note: saved configurations currently store the two axes explicitly, so I'd update the configuration structure to allow to have N y axes.

@olivhoenen please confirm you'd like me to implement this.

  1. A op B = C instead of A op B = A

After reviewing the issue, it appears that this will require significant changes to both the back end and the front end.

The application is currently built on the assumption that every displayed signal comes from a data source: a node selected in the tree, with its own URI, name (by default), unit and metadata. That link to a source is what drives the plot's name, what the metadata panel displays, what gets persisted in a configuration and what is reloaded when the configuration is opened again.

A signal produced by an operation has no such source. It exists only as the result of a computation. Supporting a signal that stands on its own means adapting across the whole chain: naming, tabs, metadata, customization and persistence. This is a real design change rather than an addition, and it isn't work we had anticipated.

There may be a backend aspect as well, and I'd rather not state anything definitive here. From what I could see, the operands of a signal operation are re-fetched from their URI, which suggests that a signal that is itself the result of an operation may not be usable as an operand. Chaining from the original signal seems fine — A * B / D can be expressed as an ordered list of operations applied to A. But referring to the result itself, for instance C / D, or combining two computed signals C1 / C2, might require an API evolution. @jwasikpsnc could you confirm whether that's the case?

Given all this, I'd rather keep A op B = A in this PR and open a dedicated issue for A op B = C, so we could scope the frontend and backend work properly and implement it at a later time. Would that work for you?

  1. UI clarity

Agreed, I'll apply both suggestions: rename the field description to "Select the operand", and add a short explanation of what A and B are for signal operations.

About selecting A through the tabs: that logic is a direct consequence of A op B = A, so it would naturally disappear when point 3 will be implemented: unary and binary operations will no longer update signals they used as operands. So, A and B would both be picked explicitly when creating C.

The solution you propose to improve the UX is a temporary solution and will be replaced when point 3 will be done.

@imbeauf FYI

@olivhoenen

Copy link
Copy Markdown
Contributor

Thanks for the detailed review. Answers point by point:

  1. Why constants are always applied before signal operations

This isn't a UI choice, it's a backend constraint. The two features (unary and binary operations) travel through two distinct query parameters (operations and signal_operations).

So the request order can't be honoured whatever the frontend sends. Lifting this would require a backend change: a single ordered parameter where each entry is either type:value or type:uri, applied in request order. @jwasikpsnc Do you confirm it?

I don't know if it it's doable in this scope or if it's a feature. I'd suggest tracking that as a separate backend issue rather than folding it into this PR. In the meantime the UI only shows that warning when both kinds are actually mixed.

Fine by me, @jwasikpsnc please confirm so I can make an issue for reference

@olivhoenen

Copy link
Copy Markdown
Contributor
  1. Units

You're right, and it's on the frontend side. The backend is fine, it does return the updated unit in the response. The frontend stores it on the plot, but the unit displayed in the axis title and in the metadata panel is read from the axis (yAxisData.unit), which I never update. I'll fix that.

However, a new unit coming out of an operation causes a problem, because we currently have a hard limit of 2 Y axes, hence 2 different units per grid. To display the result of an operation, that constraint has to go.

This is doable: plotly.js supports more than 2 Y axes, and the version we bundle handles the positioning of the extra axes automatically. The trade-off is readability: a grid with many Y axes becomes harder to read, and each extra axis eats into the plot area.

Here is what it would look like: image

As a side benefit, this also lifts the limitation when adding signals from the tree: users would no longer be capped at 2 different units.

One thing to note: saved configurations currently store the two axes explicitly, so I'd update the configuration structure to allow to have N y axes.

@olivhoenen please confirm you'd like me to implement this.

Before going to a much more general solution (which would be a new feature request/enhancement), we should still be able right now to have the new field/unit at the second y-axis (assuming a single unit for all curves initially on the plot), and error otherwise.

@olivhoenen

Copy link
Copy Markdown
Contributor
  1. A op B = C instead of A op B = A

After reviewing the issue, it appears that this will require significant changes to both the back end and the front end.

The application is currently built on the assumption that every displayed signal comes from a data source: a node selected in the tree, with its own URI, name (by default), unit and metadata. That link to a source is what drives the plot's name, what the metadata panel displays, what gets persisted in a configuration and what is reloaded when the configuration is opened again.

A signal produced by an operation has no such source. It exists only as the result of a computation. Supporting a signal that stands on its own means adapting across the whole chain: naming, tabs, metadata, customization and persistence. This is a real design change rather than an addition, and it isn't work we had anticipated.

There may be a backend aspect as well, and I'd rather not state anything definitive here. From what I could see, the operands of a signal operation are re-fetched from their URI, which suggests that a signal that is itself the result of an operation may not be usable as an operand. Chaining from the original signal seems fine — A * B / D can be expressed as an ordered list of operations applied to A. But referring to the result itself, for instance C / D, or combining two computed signals C1 / C2, might require an API evolution. @jwasikpsnc could you confirm whether that's the case?

Given all this, I'd rather keep A op B = A in this PR and open a dedicated issue for A op B = C, so we could scope the frontend and backend work properly and implement it at a later time. Would that work for you?

I see the current limitation of the interactions with the backend, and will make a separate issue for ref. Nevertheless, having the capability at the frontend side to change the label of each curve/signal (was discussed in the past, but so far only the change of title was implemented) shall be possible, hopefully not to hard to implement (without changing the backend) and have applications outside of this specific data transforms. I will make another issue specifically for one.

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.

The transposition is not preserved after a data manipulation

3 participants