-
Notifications
You must be signed in to change notification settings - Fork 127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FigureData wrapper for figures #814
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still missing:
- Save the figure and not the data class (I could run in my environment after I made a dirty fix).
- Sync the
name
data member with thefigure_names
array. For example, when the user modifies the figure's name, by modifying one of them. Alternatively block such modifications. - Tests.
All fixed. For |
self._name = name | ||
self.metadata = metadata if metadata is not None else {} | ||
|
||
# name is read only |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there potential issues with the user modifying figure_names
after a figure was created?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering this too. In theory couldn't you rename a figure by using this?
I expect it would have some headaches in how you propagate it through to the DB since you would likely need to track the old figure name to delete when saving under the new figure name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree; so for now, the user cannot change name
.
Co-authored-by: Yael Ben-Haim <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few suggestions, but I think this is headed in the right direction
self._name = name | ||
self.metadata = metadata if metadata is not None else {} | ||
|
||
# name is read only |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering this too. In theory couldn't you rename a figure by using this?
I expect it would have some headaches in how you propagate it through to the DB since you would likely need to track the old figure name to delete when saving under the new figure name.
@@ -130,6 +129,36 @@ def __json_encode__(self): | |||
return self.__getstate__() | |||
|
|||
|
|||
class FigureData: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could probably just be called Figure
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also add a __repr__
or _repr_html_
method that prints the name and displays the actual figure (not exactly sure how to do this). This should work whether the figure is a MPL figure, or a loaded SVG from database (later can is IPython.SVG I think).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Figure
is already taken by pyplot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Figure
will cause confusion with pyplot's figure; also, this requires changing the way figures are referenced all over the code.
def copy(self, new_name: Optional[str] = None): | ||
"""Creates a deep copy of the figure data""" | ||
name = new_name if new_name is not None else self.name | ||
return FigureData(figure=self.figure, name=name, metadata=copy.deepcopy(self.metadata)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a deep copy if the figure isn't also deep copied. If you want a deepcopy why not just do copy.deepcopy(self)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is needed since we don't allow yet changing the name, but when copying might want to change it. I'll deepcopy the figure as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just tried it and deep copying the figure causes matplotlib errors.
"""Initialize a figure data from the json representation""" | ||
return cls(**args) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it could be nice to add a save
and load
method to this class for saving the the figure to a file or the DB.
While this wouldn't be supported yet a save method for DB would be useful if you change filename or add metadata and to save to update DB.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what those functions should do now. Their existence might trick the user to think the data is saved to the DB.
self._figures[figure_key] = figure_data | ||
|
||
if figure_data is None: | ||
raise DbExperimentEntryNotFound(f"Figure {figure_key} not found.") | ||
|
||
if file_name: | ||
with open(file_name, "wb") as output: | ||
num_bytes = output.write(figure_data) | ||
num_bytes = output.write(figure_data.figure) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This writing to binary could be moved to the save
method of Figure class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should also be some LOG.warning msgs that if you try and save a figure with metadata that the metadata is not currently saved to DB
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a warning, not sure about the save
method; in this context we are saving the figure obtained from the DB into a file on the local computer.
--- | ||
features: | ||
- | | ||
Figures added to `ExperimentData` are now wrapped with a `FigureData` class |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be better as an upgrade note. Something like:
Adds a :class:`.FigureData` class for adding metadata to a analysis result figures. Figures added to
`ExperimentData` are now stored using this class. The raw image object (SVG or matplotlib.Figure)
can be accessed using the :attr:`.FigureData.figure` attribute.
Note that currently metadata is only stored locally and will be discarded when saved to the cloud
experiment service database.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Couple minor changes inc adding the new class to the qiskit_experiments.framework
API docs.
"""Initialize a figure data from the json representation""" | ||
return cls(**args) | ||
|
||
def _repr_png_(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason for having both png and svg reprs? Does svg repr not work well for MPL figures?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, with svg sometimes there were missing things from the diagram. I played with it and it seems to me png was the best method I could find to consistently give good results.
|
||
def _repr_png_(self): | ||
if isinstance(self.figure, MatplotlibFigure): | ||
b = io.BytesIO() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this should be cached so the conversion doesn't happen everytime you view it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure we need this - it is very quick, and we don't display many images at once, certainty not several times for the same image.
# currently only the figure and its name are stored in the database | ||
if isinstance(figure, FigureData): | ||
figure = figure.figure | ||
LOG.warning("Figure metadata is currently not saved to the database") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LOG.warning("Figure metadata is currently not saved to the database") | |
if figure.metadata: | |
LOG.warning(f"Figure {figure.name} metadata cannot be saved to the experiment service.") |
This only needs warning if the metadata is not empty, otherwise there will be warnings raised everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically you are correct, but in practice the metadata will never be empty, since it has a qubits
field with the physical_qubits
from the experiment itself (this was needed for qiskit-monitoring, the initial motivation for this PR). Also, it probably should always be initialized and not None
even without it.
So right now we will get warnings everywhere, even with your suggested fix. Not sure what's the best way to go about this - I'm not sure we need this warning.
Co-authored-by: Christopher J. Wood <[email protected]>
Co-authored-by: Christopher J. Wood <[email protected]>
Co-authored-by: Christopher J. Wood <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the wrong logger msg was changed from warning to debug
* Added FigureData wrapper for figures * FigureData is now kept when adding the figure into a composite analysis result * Update qiskit_experiments/database_service/db_experiment_data.py Co-authored-by: Yael Ben-Haim <[email protected]> Co-authored-by: Christopher J. Wood <[email protected]>
* Added FigureData wrapper for figures * FigureData is now kept when adding the figure into a composite analysis result * Update qiskit_experiments/database_service/db_experiment_data.py Co-authored-by: Yael Ben-Haim <[email protected]> Co-authored-by: Christopher J. Wood <[email protected]>
Summary
This PR adds a
FigureData
wrapper around figures added toExperimentData
to enable saving additional metadata (e.g. qubits) with the figure. This data is currently not saved in the result database.Details and comments