### JS9 on the Desktop

JS9 can be used as a desktop replacement for SAOimage DS9: you can load images into the app's configurable web page (or your own custom web page) and use the full power of JS9, including external messaging.

Advantages of using JS9 on the desktop include:

- built using today's web-based technology
- fully configurable user interface using standard web pages
- support for adding your own browser-based analysis plugins
- complete programming interface via a rich public API
- external messaging with full access to the public API (Python and shell)
- flexibility of having multiple images associated with separate displays
- support for image blending
- support for image reprojection

Advantages of using DS9 include:

- the de facto standard astronomical image display for nearly three decades
- full access to virtual memory (no memory limits when loading huge files)
- support for nearly all WCS systems via the AST library
- support for 3D rendering
- advanced access to external catalogs and archives
- XPA-based messaging integrated with CIAO, ftools, and Python
- support for VO/SAMP

### Installing a Pre-built Desktop App

Pre-built desktop apps for Linux (for Ubuntu 20.04 and compatible systems) and Mac (for Catalina 10.15.x) are available on the main JS9 web site:

- js9app: offers a basic user interface
- Voyager: coming soon ... offers an advanced user interface patterned after the Mac

For Linux, download and unzip the zip file to create a directory called <app>-linux-x64 containing a number of files, including a js9app executable. Put this directory in your Linux PATH (or use a link to the executable) and run the program. For Mac, download the zip file and unzip it into your /Applications or ~/Applications directory to create a Mac application, which you can start by double-clicking or by using the open command:

      # open Mac app, load FITS file and region file, set colormap and scale
      open /Applications/js9.app --args ~/data/casa.fits --regions ~/data/casa.reg --colormap viridis --scale log

Once the app is started, you can drag and drop a FITS file to display it (or use the File->open menu option, etc). The File->export messaging script menu option will bring up a dialog box to create a script called js9msg that you can use to control the app externally (see External Communication with JS9 for details.)

### Installing Electron.js for a Fully Configurable Desktop App

The pre-build apps described above are built using Electron.js, a widely-used framework for creating native applications with web technologies like JavaScript, HTML, and CSS. You can easily configure your own JS9 app (giving you complete control over the display web page, server-side analysis, etc.) by installing Electron.js.

Install Electron.js by visiting the Electron.js release page: https://github.com/electron/electron/releases to download the latest available stable (not beta) release for your platform. On a Mac, the Electron.app should be installed in the /Applications or ~/Applications folder. On Linux, the electron program should be placed in your PATH. Electron.js also is available for Windows, so desktop JS9 should also run on that OS, although we have not done any work in this direction. If you get desktop JS9 running under Windows, please let us know!

Once the Electron.js is installed, you can build JS9 as usual, taking care to configure use of the Node.js helper. Note that there is no need to actually install Node.js: desktop JS9 has its helper integrated into Electron.js already.

If you are not planning to utilize server-side analysis tasks or large file support, you can even skip the standard build: edit the supplied js9prefs.js file (for the browser) and a js9Prefs.json file (for the JS9 helper) to add your preferred JS9 properties, and use the supplied js9 script to start the desktop.

Note for macOS users: if you plan to use Electron.app with JS9 on a Mac, consider codesign'ing the Electron.js app:

      sudo codesign --force --deep --sign - /Applications/Electron.app/Contents/MacOS/Electron

to avoid repeated requests to allow incoming connections.

### Running JS9 on the Desktop

The js9 script is normally made accessible by adding the JS9 install directory (when fully building JS9) or the source directory (for quick install) to your user PATH.

Run the js9 script with the -a switch to start the desktop app, display the default JS9 web page, and load one or more FITS files:

      # the -a switch tells the script to bring up the desktop js9 app
      js9 -a ~/data/casa.fits

      # opts can be passed via json format
      js9 -a fits/casa.fits '{"scale":"log","scalemin":3,"colormap":"cool"}'

      # opts can be passed via switch format
      js9 -a fits/casa.fits --scale log --scalemin 3 --colormap cool

      # opts can be passed via mixed format
      js9 -a fits/casa.fits --colormap cool '{"scale":"log","scalemin":3}'

      # multiple files can have different opts
      js9 -a fits/casa.fits.gz --colormap heat --scale log fits/casa.fits --colormap cool --ra 350.8667 --dec 58.812

      # load a colormap file, then use the newly loaded colormap for the image
      # also load a regions file
      js9 -a cmaps/purple.cmap fits/casa.fits --colormap purplish --regions casa/casa.reg

In the desktop app, all relative paths are relative to current working directory, as would be expected with files passed to any desktop program. For consistency, this behavior extends to the case of files specified in web pages: relative files are still relative to the current directory, not the web page (as would be the case with browsers). It is controlled globally by the JS9.globalOpts.currentPath property and locally by the fixpath property. For example, if the desktop app loads a webpage, then the default call to JS9.Load():

    <a href='javascript:JS9.Load("fits/casa.fits", {scale:"log", colormap: "cool"});'>CAS-A</a>

specifies that the FITS file is relative to the current working directory, while use of fixpath:false:

    <a href='javascript:JS9.Load("fits/casa.fits", {scale:"log", colormap: "cool", fixpath:false});'>CAS-A</a>

specifies that the FITS file is relative to the web page.

You also can use the ${JS9_INSTALLDIR} and ${JS9_PAGEDIR} "macros" to specify that the path of the FITS file is relative to the JS9 install directory or the web page directory, respectively. For example:

    <a href='javascript:JS9.Load("${JS9_PAGEDIR}/fits/casa.fits", {scale:"log", colormap: "cool"});'>CAS-A</a>

specifies that the FITS file is relative to the web page (just like fixpath), while:

    <a href='javascript:JS9.Load("${JS9_INSTALLDIR}/fits/casa.fits", {scale:"log", colormap: "cool"});'>CAS-A</a>

specifies that the FITS file is relative to the JS9 install directory.

Note that path specification using fixpath, ${JS9_INSTALLDIR}, or ${JS9_PAGEDIR} applies to the "Load" routines:

- JS9.Load()
- JS9.LoadCatalog()
- JS9.LoadColormap()
- JS9.LoadRegions()
- JS9.LoadSession()

The same js9 script (without the -a switch) can now be used to interact with the JS9 page (or any other JS9-enabled web page):

      # without -a, the script sends commands to the JS9 display
      js9 SetColormap cool
      js9 AddRegions 'ICRS;ellipse(23:23:18.76, +58:47:27.252, 31.8", 15.9", 40)'

See: External Messaging for more details.

You can also load remote images, as the script will call LoadProxy as needed:

      js9 -a http://hea-www.cfa.harvard.edu/~eric/coma.fits.gz

A number of desktop-specific switches are available in the js9 script. Perhaps the most important is the --webpage switch, which allows you to specify a custom web page to display, so that you can tailor the desktop app to your specific needs:

      js9 -a --webpage ~/myjs9/myjs9.html ~/data/casa.fits

When configuring your own web page, one simple possibility is to create a separate directory, parallel to the JS9 source (or install) directory, in which you can maintain your custom web page(s) and your customized js9prefs.js file. You might also create a myjs9 script that runs the js9 script. For example, this myjs9.html file might be stored in a myjs9 directory parallel to the js9 directory:

      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
         "http://www.w3.org/TR/html4/loose.dtd">
      <html>
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=Edge;chrome=1" >
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link type="image/x-icon" rel="shortcut icon" href="../js9/favicon.ico">
        <link type="text/css" rel="stylesheet" href="../js9/js9support.css">
        <link type="text/css" rel="stylesheet" href="../js9/js9.css">
        <script type="text/javascript" src="js9prefs.js"></script>
        <script type="text/javascript" src="../js9/js9support.min.js"></script>
        <script type="text/javascript" src="../js9/js9.min.js"></script>
        <script type="text/javascript" src="../js9/js9plugins.js"></script>
        <title>my JS9 app</title>
      </head>
      <body>
          <div class="JS9Menubar" data-width="100%"></div>
          <p style="margin-top: -14px;">
          <table cellspacing="0" style="width:100%;">
          <tr valign="top">
          <td align="left">
          <div class="JS9" data-width="768px" data-height="768px"></div>
          <div style="margin-top: 2px;">
          <div class="JS9Colorbar" data-width="768px" id="JS9Colorbar" data-showTicks="false" data-height="10px"></div>
          </div>
          </td>
          <td align="right">
          <table cellspacing="0">
          <tr valign="top">
          <td>
          <div class="JS9Magnifier" data-width="250px" data-height="250px"></div>
          </td>
          </tr>
          <tr valign="top">
          <td>
          <div class="JS9Panner" data-width="250px" data-height="250px"></div>
          </td>
          </tr>
          <tr valign="top">
          <td>
          <div class="JS9Info" data-height="250px" style="margin-top: 2px;"></div>
          </td>
          </tr>
          </table>
          </td>
          </tr>
          </table>
      </body>
      </html>

Note that the JavaScript and CSS files are loaded from the js9 source (or install) directory, but the js9prefs.js is loaded from the myjs9 directory. This separation allows you to configure site-wide js9 parameters without changing the any of the files in the source directory, and allows you to update the source directory very easily by executing "git pull".

A script such as the following can then be used to use this web page in the JS9 desktop:

      #!/bin/bash

      WEBPAGE="$HOME/myjs9/myjs9.html";

      WIDTH=1130;
      HEIGHT=860;

      if [ x${JS9_WEBPAGE} != x ]; then
        WEBPAGE=${JS9_WEBPAGE}
      fi

      if [ x${JS9_WEBPAGE_WIDTH} != x ]; then
        WIDTH=${JS9_WEBPAGE_WIDTH}
      fi

      if [ x${JS9_WEBPAGE_HEIGHT} != x ]; then
        HEIGHT=${JS9_WEBPAGE_HEIGHT}
      fi

      exec $HOME/js9/js9 -a --width $WIDTH --height $HEIGHT --webpage $WEBPAGE $*

As shown above, the --width and --height switches are available to set the width and height of the Electron.js window which will contain the web page.

Another important switch is --title (and its generalized cousin, --renameid). This switch will rename the main JS9 display id in the web page (whose default is "JS9") to the specified id. It is useful in cases where you want to start up multiple desktops using the same web page, and communicate with each one separately. In such cases, the --title switch will change the id of the JS9 display element and its auxiliary elements (e.g. menubar, colorbar, etc) to the specified title:

      js9 -a --title foo1 ~/data/casa.fits

You will then be able to communicate with this web page using the specified id:

      js9 --id foo1 GetColormap
      {"colormap":"heat","contrast":1,"bias":0.5}

The --renameid switch allows you to specify multiple JS9 displays to rename, in cases where more than one JS9 display is part of a web page:

      js9 -a --renameid "JS9:foo1,myJS9:foo2" ~/data/casa.fits

will rename the default "JS9" element to "foo1" and the "myJS9" element to "foo2".

The --savedir switch will set the directory into which files are saved, avoiding the display of an interactive dialog box when saving images:

      js9 -a --savedir /Users/eric/Desktop ~/data/casa.fits
      ...
      js9 --id foo1 SavePNG casa.png

will save the casa.png file on the desktop without a dialog box. This is especially useful in automatic scripting.

You can use the --cmds [cmds] and/or --cmdfile [file] switches to pass Javascript commands that will be executed when JS9 is ready and all files have been loaded. The former takes a string of commands as an argument:

      # load a file and set the colormap and scale
      js9 -a --cmds 'JS9.SetColormap("cool");JS9.SetScale("log")' ~/data/casa.fits

The latter takes a file containing commands, allowing you to perform more sophisticated processing. For example, the following script will load the Chandra image of the Kes 75 supernova remnant, display three energy cuts as separate images, assign red, green, and blue colormaps to the three energy cuts, and then blend them into a single display:

      # run the script in the command file
      js9 -a --cmdfile eband.js

      // where eband.js contains the following Javascript:
      JS9.Load("kes75/kes75_evt2.fits.gz", {onload: function(im){
          var i;
          // colormaps
          var c = ["red", "green", "blue"];
          // energy filters
          var f = ["energy=500:1500", "energy=1500:2500", "energy=2500:8000"];
          // set final configuration after each image is loaded
          var mkdo = function(i){
      	return function(xim){
      	    JS9.SetScale("log",   {display: xim});
      	    if( c[i] ){ JS9.SetColormap(c[i], {display: xim}); }
      	};
          };
          // turn blending off on the main image
          JS9.BlendImage(false);
          // process each of the event filters to make a separate image
          for(i=0; i<f.length; i++){
      	// display filtered image in a separate displayed
      	JS9.DisplaySection({filter:f[i], separate:true,
      			    ondisplaysection: mkdo(i)}, {display: im});
          }
          //  blend the filtered images
          JS9.BlendDisplay(true);
      }});

The --merge switch allows you to utilize another user's setup, including their JS9 web page and analysis routines. Say, for example, a colleague has used Dropbox to share her JS9-enabled zhjs9 directory, containing the following files and sub-directories:

      zhjs9.html js9prefs.js js9addons.js

      analysis-plugins:
      zhtools.json

      analysis-wrappers:
      zhjs9

      params:
      adapt.html	imexam.html	mexhat.html
      atrous.html	imsmo.html	refinepos.html

where zhjs9.html has a header in which the paths to JS9's files are matched to your colleague's setup, but not your own:

      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=Edge;chrome=1" >
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link type="image/x-icon" rel="shortcut icon" href="../../js9/favicon.ico">
      <link type="text/css" rel="stylesheet" href="../../js9/js9support.css">
      <link type="text/css" rel="stylesheet" href="../../js9/js9.css">
      <link rel="apple-touch-icon" href="../../js9/images/js9-apple-touch-icon.png">
      <script type="text/javascript" src="js9prefs.js"></script>
      <script type="text/javascript" src="../../js9/js9support.min.js"></script>
      <script type="text/javascript" src="../../js9/js9.js"></script>
      <script type="text/javascript" src="../../js9/js9plugins.js"></script>
      <script type="text/javascript" src="js9addons.js"></script>
      </head>

Also note the presence of analysis tool definitions and scripts in the analysis-plugins, analysis-wrappers, and params sub-directories. Normally, in order to use the zhjs9 web page and associated analysis tools, you would need to edit the former and change the JS9 paths, and move the contents of the three analysis tools sub-directories into the appropriate sub-directories in the main JS9 install directory.

Instead, you can simply merge this directory into your desktop app, e.g.

      js9 -a --merge ~/Dropbox/zhjs9/zhjs9.html

This will generate and load a temporary webpage using correct paths to the JS9 install directory and load the analysis tools into the JS9 helper. You can also merge the analysis tools without loading the web page by specifying only the directory:

      js9 -a --merge ~/Dropbox/zhjs9

In addition, if a bin directory is present, it will be added to the PATH used when processing analysis commands.

A merged web page can, of course, include its own javascript and css files, as shown in the example above. It can also include JS9.Load() commands, for example, to load files from a subdirectory. In this case, you should set the fixpath property to false so that the paths of the data files are not changed into paths relative to the current working directory (which is the default behavior for desktop JS9.Load() calls):

    <a href='javascript:JS9.Load("fits/casa.fits", {scale:"log", colormap: "cool", fixpath:false});'>CAS-A</a>

Finally, the --hostfs switch (NB: was --node prior to v3.1) allows you to access the host file system in a local (only, not remote) web page environment. By default, this feature is turned off because it requires turning off the Electron's Context Isolation security feature, providing a greater attack surface for hackers. But since this host access is permitted only for local web pages, an attacker would have to be on your system ... so you're probably in big trouble anyway.

That said, there are two good reasons for turning on host file system access:

- to enable direct access to data files in the local file system
- to run scripts that require access to system resources

When access to the host file system is enabled, the JS9 app will (subject to the boolean value of the JS9.globalOpts.localAccess property) mount the local file system inside the web page and access FITS files directly, instead of fetching and storing them in browser memory. This can speed up the load/display time considerably, while minimizing the use of a browser memory. The list of file extensions which are accessed directly in this way is specified by the JS9.globalOpts.localTemplates property, which defaults to .fits and .fts. Note that bzip'ed (.bz2) and gzip'ed (.gz) files are not accessed directly: the former are not supported by the CFITSIO FITS access library, while the latter are supported by uncompressing the file in memory, which is done more efficiently by JS9 itself. Also, symbolic links currently are not accessed directly. We expect to remove this restriction in the near future.

You can also run scripts that access local system resources. For example, the following script will load the Chandra image of the Kes 75 supernova remnant, display three energy cuts as separate images, find the total number of counts in each image, and write the results to a log file, using the Node.js 'fs' module.

      # enable host file system support and run the script in the command file
      js9 -a --hostfs true --cmdfile ecnts.js

      // where ecnts.js contains the following Javascript:
      var fs;
      try{
          fs = require("fs");
      }
      catch(e){
          JS9.error("Node.js 'fs' module is unavailable. Did you enable hostfs?");
      }
      JS9.Load("kes75/kes75_evt2.fits.gz", {onload: function(im){
          let i;
          let s = "";
          let got = 0;
          // energy filters
          const f = ["energy=500:1500", "energy=1500:2500", "energy=2500:8000"];
          // get counts in regions as each image is displayed
          const getcnts = function(i){
        	return function(xim){
      	    s += xim.countsInRegions();
      	    got++;
      	    if( got === 3 ){
                    // write the results to a log file
      		fs.writeFile("countsInRegions.log", s, function(err) {
    		    if( err ) { JS9.error(err); }
      		});
      	    }
        	};
          };
          // process each of the event filters to make a separate image
          for(i=0; i<f.length; i++){
        	// display filtered image in a separate displayed
        	JS9.DisplaySection({filter:f[i], separate:true,
        			    ondisplaysection: getcnts(i)}, {display: im});
          }
      }});

For a list of all js9 script switches, use the --help switch:

      js9 --help

The JS9 File menu contains two options only available for Desktop use:

- print window ... print the entire Electron.js window
- save window to pdf save the entire Electron.js windows to a PDF file called js9.pdf in the current directory

The print command always brings up a dialog box. The save command will save the window as a PDF in the current directory, without bringing up a dialog box.

### Security Notes

It is important to note that Electron.js is not a web browser, and web pages you load are not sandboxed. Our JS9 desktop application code takes additional precautions to enhance security:

- http protocol is disabled for remote pages
- Javascript eval is disabled in web pages
- the ability to click a link to navigate to a new web page is disabled
- the ability to click a link to display to a new web page in a different tab or window is enabled, but the new web page is displayed in your default browser
- context isolation is turned on, unless host file system support is explicitly enabled (--hostfs)

Even with these safeguards in place, it is important that you load only local or trusted remote web pages into the JS9 desktop app. See: Electron.js security for more information.

You should update your copy of Electron.js periodically to ensure that you have the latest security fixes in place.

##### Last updated: October 30, 2020