pyGMT finding old ghostscript with conda on Windows

I am installing pygmt using anaconda navigator on Windows, and running it in jupyter-lab. Nominally, it seems to work fine; I get sensible output for the version info:

import pygmt
pygmt.show_versions()

PyGMT information:
  version: v0.7.0
System information:
  python: 3.9.12 (main, Apr  4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)]
  executable: C:\Users\eol\Anaconda3\envs\escape\python.exe
  machine: Windows-10-10.0.19043-SP0
Dependency information:
  numpy: 1.23.1
  pandas: 1.4.3
  xarray: 2022.6.0
  netCDF4: 1.6.0
  packaging: 21.3
  geopandas: None
  ghostscript: 9.54.0
  gmt: 6.4.0
GMT library information:
  binary dir: C:/Users/eol/Anaconda3/envs/escape
  cores: 12
  grid layout: rows
  library path: C:/Users/eol/Anaconda3/envs/escape/Library/bin/gmt.dll
  padding: 2
  plugin dir: C:/Users/eol/Anaconda3/envs/escape/Library/bin/gmt_plugins
  share dir: C:/Users/eol/Anaconda3/envs/escape/Library/share/gmt
  version: 6.4.0

However, when I run the example plot commands (or any other plot), I get an error related to a wrong version of ghostscript, caused by fig.show(): there is a long traceback which ends in the following:

File ~\Anaconda3\envs\escape\lib\site-packages\pygmt\figure.py:248, in Figure.psconvert(self, icc_gray, **kwargs)
    245     raise GMTInvalidInput("The 'prefix' must be specified.") from err
    247 with Session() as lib:
--> 248     lib.call_module(
    249         module="psconvert", args=f"{prefix_arg} {build_arg_string(kwargs)}"
    250     )

File ~\Anaconda3\envs\escape\lib\site-packages\pygmt\clib\session.py:506, in Session.call_module(self, module, args)
    502 status = c_call_module(
    503     self.session_pointer, module.encode(), mode, args.encode()
    504 )
    505 if status != 0:
--> 506     raise GMTCLibError(
    507         f"Module '{module}' failed with status code {status}:\n{self._error_message}"
    508     )

GMTCLibError: Module 'psconvert' failed with status code 79:
psconvert [ERROR]: System call [@"C:\Program Files (x86)\GPLGS\gswin32c.exe" -q -dNOPAUSE -dBATCH -dNOSAFER -dMaxBitmap=2147483647 -dGraphicsAlphaBits=2 -dTextAlphaBits=2 -sDEVICE=png16m  -g136530x136530 -r300 -sOutputFile="C:\Users\eol\AppData\Local\Temp\2e939da376b147f888e603804d90dbb6-preview-_930b8h4\2e939da376b147f888e603804d90dbb6.png" "C:/Users/eol/.gmt/sessions/gmt_session.6632/psconvert_14224d.eps"] returned error -1073741819.
​

This error seems to be caused by psconvert finding a very old version of gs, located at C:\Program Files (x86)\GPLGS\gswin32c.exe. I found that this ghostscript does indeed exist, but is version 8.15 from 2004!

I can’t easily uninstall this version of gs (it may be needed by something else), and am wondering if there is a way to redirect psconvert to find the correct, 9.54 version of gs, which was installed as a dependency in the conda directory? It seems like a bug, psconvert should be set up to only ever find this version and not go hunting around the computer for old ones.

psconvert finds first whatever is in the Windows registry but there is an option (-G ?) that can be used to indicate where gs lives. However, the best is probably to install the 64 bits version, which will then be picked first then the 32 that you have.

Thanks. The 64-bit version of gs is already installed in the conda environment directory (something like C:/Users/eol/Anaconda3/envs/escape/Library/bin/gs), but it must not have been added to the registry (I expect for reasons of maintaining multiple environments, conda doesn’t edit the registry on principle?).

So is it possible to pass this -G flag from fig.show() to psconvert? The help message doesn’t indicate so… Maybe I could get it to work via fig.psconvert() which passes any arguments on, but this defeats the goal of viewing the figure in jupyter.

Seems like this would be a universal problem for anyone installing pyGMT via conda on windows, if they had any other gs installed previously. I’m not sure why more people haven’t had this error, or am I doing something wrong in my setup?

Maybe I am misunderstanding your suggestion, do you mean I should install the 64-bit ghostscript via it’s own installer, such that it (hopefully) gets added to the registry? Seems like that could work, I’ll give it a try tomorrow.

I still think that the conda version of GMT should be able to find the matching conda ghostscript, though. It seems pygmt.show_versions() is able to find it, but not psconvert.

Well, this is a little bit like a UNIX PATH problem, no? If you had an old gs in your path and it is listed ahead of the one you want you will still get the wrong gs. It may be possible to add a lot of code to examine dates and versions and try to find the most recent despite what the PATH (and registry) says, but since the expectation is for the user to have a clean system with just one gs version we push that problem back on you, Eric! As for conda version of GMT: It is the same GMT and it does not know it is access from a conda environment (I think ).

When you open a new terminal from JupyterLab and run echo ${PATH}, does the path for the old ghostscript show up before the conda environment path?

fyi conda init, which can be run during Anaconda installation, modifies your shell configuration so that the bin dir for the base environment is included in the path. conda activate env will add the bin dir for env immediately before the base environment. If the old ghostscript is found first, that will be used.

Thanks @pwessel - agreed it is just like a path issue! But, I guess I am surprised that pygmt.show_versions() finds the correct gs, while psconvert finds the wrong one. @maxrjones’ suggestion helps me understand why: the path is indeed set correctly, which I can verify with $Env:Path on the PowerShell terminal opened from jupyter-lab; and typing ‘gs’ from this terminal also finds the correct gs. But, the problem is that psconvert on windows finds gs from the registry instead of from the path.

My first suggestion is that psconvert could be changed to read the location of gs from the path like it does on other OSes, instead of the registry (or it could check both). Or alternately, pygmt’s show() and savefig() could be set up to pass the -G flag to psconvert after figuring out where the conda version of gs is located. The functionality for that is already there, just has to be connected up.

Otherwise, for now the workaround seems to be installing a recent gs from outside conda, and hoping that it overrides any previous gs entries in the registry. But the gs installer requires admin access to run, so I can’t try this out on the computers in the classroom lab I’m working in. So, for now I’m stuck.

psconver must seek the registry because the windows installer does not add gs to the path.
When no gs is found gmt uses the it’s own shipped ghost. Your problem is that an very old ghost is found in the registry and one that apparently doesn’t work. Other from that I don’t see any problem that is not easily fixable.

Edit: just remembered. Since you are not using the official GMT installation there’s probably no backup ghost installation.

I dont know if the psconvert.exe Windows binary can learn that it is being called from PyGMT and not Windows bash or bat and then look in PATH instead of calling the registry fishing, @Joaquim?

In principle it should know if it’s being called from one external but not which external. But again, why would we do it? The problem here is not pyG or Conda but an old 32 bits version that sits in the registry.

Agreed

Couldn’t psconvert simply check for gs in the path first, and if not there, check the registry? Since the conda gs installer adds it to the path but not the registry. This seems like it would solve the current problem without breaking other installations.

Alternately, giving show() and savefig() the ability to pass -G flags through to psconvert would also solve my issue, though it would require manually adding that flag in every call to that command.

For now, since uninstalling the old gs or installing a new one outside of conda isn’t an option on these computers, I’m forced to give up on GMT and pyGMT entirely for my assignments this semester.

I agree with the utility of adding an alias for -G in show/savefig/psconvert. Please feel free to open an issue requesting this in the PyGMT GitHub repository.

Eventually (by v1.0.0), single character options will be disallowed in PyGMT, but in the meantime you could workaround this issue using:

fig.savefig('test.png', G="C:/Users/eol/Anaconda3/envs/escape/Library/bin/gs")

Unfortunately, this would not work for in-line previews using fig.show.

As for the oddness of the result from pygmt.show_versions, the show_versions logic simply checks for a 64bit version and then for a 32bit version on Windows since unlike on unix the name is gswinXXc rather than just gs.

Thanks - will do. Interestingly, the conda version of gs that gets installed on windows is just “gs” and not the longer name. So I’m surprised it still found the right version! But it’s good news, if show_versions can find it then the other functions should be able to as well.

Remember that show_versions is written in Python and is fully aware of the conda environment, while psconvert.exe is a Windows executable unaware of any conda environments.

I think it is possible to check if there is a PATH defined in the environment and look for gs even on Windows, but now we would have to step through a list of directories in the PATH and see if there is a gs in each one, and we have no guarantee that this gs is the one that is most recent. So that would mean we would need to call it and capture the version and keep track of versions and at the end pick the most recent. Lots of work on us because some administrator failed to do a good job somewhere else. I understand this is not your fault but it cannot become our problem either.

I think there is a comment in the psconvert.c code that, after all others have failed, when resorting to the path it says: “pray it works” because it doesn’t check the path. Just … as it says.

Understood it’s not worth it to fix, I can see why reading the path on Windows might be harder than in other systems, since you have to implement it directly instead of just relying on the shell to figure it out. It would be nice to put a warning somewhere at least, that the user is responsible for installing their own version of ghostscript on windows, since the one installed by conda or any other package manager that does not edit the registry will not be seen or recognized.

I think you still misunderstanding one point. The ghost in registry takes priority but nothing obliges to have one there. If there was none in it the one from Conda would have been catched.
We are going to skip the search in the Win32 registry(yes, Windows has two registries) when using a 64 bits GMT. This will solve your current issue but doubt it will arrive on time for your semester. The solution for you must come from a pySolution involving -G.