JS9 on the Desktop

JS9 can be used as a desktop replacement for SAOimage DS9: you can load images into the app's 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:

Advantages of using DS9 include:

Installing for Desktop Use

Desktop JS9 is based on Electron.js, a widely-used framework for creating native applications with web technologies like JavaScript, HTML, and CSS. Install Electron.js by visiting the release page: http://electron.atom.io/releases and downloading the latest available stable 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. Note that Electron.js for Linux requires a relatively recent version of Linux: Ubuntu 12.04, Fedora 21, Debian 8, CentOS 7 (not CentOS 6). Note that 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.

Moreover, if you are not planning to utilize server-side analysis tasks or large file support, you can skip the standard build and simply generate the JS9 quick-start files:

  ./mkjs9 -q
  Editing js9Prefs.json for Node.js helper ...
  Editing js9prefs.js for Node.js helper ...
  Generating js9 script for JS9 messaging and desktop use ...

  If you plan to use Electron.app with JS9, consider codesign'ing it:

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

  This will avoid repeated requests to allow incoming connections.
The mkjs9 script will create a js9prefs.js file (for the browser) and a js9Prefs.json file (for the JS9 helper), which you can edit to add preferred JS9 properties, as well as a js9 script to start the JS9 app. On a Mac, you probably will want to codesign the Electron.app application to avoid repeated requests about incoming connections (see example above).

NB: if you are running cpu-intensive scripts that communicate with the Electron-based JS9 app, we recommend that you install Node.js as well as Electron.js, and start up the JS9 helper instead of using the built-in helper support in the Electron-based app. The startup will be considerably faster when using the Node helper.

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
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:

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 --node switch allows you to enable Node.js capabilities in a local (but not remote) web page environment. By default, this feature is turned off because, in principle, Node integration provides a greater attack surface for hackers. But since node integration 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 node integration:

When node integration 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.

With node integration, 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 node support and run the script in the command file 
  js9 -a --node 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 node?");
  }
  JS9.Load("kes75/kes75_evt2.fits.gz", {onload: function(im){
      var i;
      var s = "";
      var got = 0;
      // energy filters
      var f = ["energy=500:1500", "energy=1500:2500", "energy=2500:8000"];
      // get counts in regions as each image is displayed
      var 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:

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:

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: June 5, 2019