GenericMappingTools / pygmt

A Python interface for the Generic Mapping Tools.
https://www.pygmt.org
BSD 3-Clause "New" or "Revised" License
769 stars 222 forks source link

Copy `pygmt.Figure()` instance or object to a new variable and keep the initial variable as is #3645

Open yvonnefroehlich opened 6 days ago

yvonnefroehlich commented 6 days ago

Copying a pygmt.Figure() instance or object to a new variable can be useful, e.g., within a Jupyter notebook for re-running cells with different arguments passed to the parameters to test different visualizations. However, this seems to be not straight forward. In the example below, I would like to achieve that fig1 remains as is after adding features to fig2. Neither running fig2 = fig1 nor running fig2 = copy.deepcopy(fig1) before changing fig2 works as expected. Maybe there is a relationship to continuing the GMT session and not creating a new one?

Related to: https://github.com/GenericMappingTools/agu24workshop/pull/8#issuecomment-2495692791, https://github.com/GenericMappingTools/agu24workshop/pull/8#issuecomment-2495929549

import pygmt

size = 5

fig1 = pygmt.Figure()
fig1.basemap(projection="X5c", region=[-5, 5, -5, 5], frame=True)
fig1.show()

fig2 = fig1
# OR (both does not work as expected)
# fig2 = copy.deepcopy(fig1)
fig2.plot(x=0, y=0, style="c1c")
fig2.show()

fig1.show()  # Now fig1 also contains the circle and is identical to fig2
fig1 fig2 fig1
fig1_first fig2 fig1_second
seisman commented 4 days ago

This feature is difficult to implement. The main reason is that, GMT stores the half-baked PS file, gmt.history, gmt.conf and even inset/subplot settings in temporary files. To implement what you expect with the syntax like fig1 = fig2.copy(), we need to copy fig1-related files and make necessary changes to ensure it works for fig2, which means we have to know all the details about GMT and even have to use GMT's low-level, private functions.

joa-quim commented 1 day ago

Yes, modern mode is quite tricky and does not lets us almost no liberty to inovate. However, I am a bit confused with this behavior, but for another reason.

When you call fig1.show() shouldn't it close the PS file and delete the temporary sessions dir? How come that you can still append to fig1 if, besides surviving to show(), it had to be closed order to be displayed?

seisman commented 1 day ago

When you call fig1.show() shouldn't it close the PS file and delete the temporary sessions dir? How come that you can still append to fig1 if, besides surviving to show(), it had to be closed order to be displayed?

We're doing this in PyGMT:

gmt begin
    gmt figure figname -    # Need to pass '-' as a special format
    gmt basemap ...
    gmt coast ...

    gmt psconvert ...
    open figname.png

    gmt grdimage ...

    gmt psconvert ...
    open figname.png

gmt end

When calling Figure.show, we call psconvert to convert the PS file into the desired format and then open it in an external viewer. The PS file is still there until we call gmt end. That's why we can append more layers even after calling Figure.show.

joa-quim commented 1 day ago

But if you call psconvert before gmt end the file is not yet closed and you should get the error

psconvert [ERROR]: lixo.ps: GMT PS format detected but file is not finalized. Maybe a -K in excess? No output created.

What am I missing?

seisman commented 1 day ago

Maybe you're not in modern mode? Try this one:

gmt begin
  gmt figure map -
  gmt basemap -R0/10/0/10 -JX10c -Baf
  gmt psconvert -A -P -Tg -Ffig1

  gmt basemap -R0/5/0/5 -JX10c -Baf -X10c
  gmt psconvert -A -P -Tg -Ffig2
gmt end
joa-quim commented 1 day ago

Hmm, I see. I guess I had forgotten or didn't even know that we could do that in modern mode. I barely use it, except for the insets in Julia where fish inside some of the tmp files to learn the coordinates where to put the inset. It's a crazy mixed scheme between modern and classic.

seisman commented 1 day ago

Related codes are at https://github.com/GenericMappingTools/gmt/blob/39cff6e579e7e5e1b17692122ce15e98e6da109b/src/psconvert.c#L1818.

joa-quim commented 1 day ago

Ah, so it was a forgotten thing since psconvert.c is one of the codes that I visit more often.

And speaking of which, it would be nice if you could use the psconvert-no-file-dup branch (the one where psconvert edits the ps file directly instead of making a copy) in your PyGMT tests. I've been using it in Julia and found no problems so far.

seisman commented 1 day ago

And speaking of which, it would be nice if you could use the psconvert-no-file-dup branch (the one where psconvert edits the ps file directly instead of making a copy) in your PyGMT tests. I've been using it in Julia and found no problems so far.

OK. Will give it a try.