Saturday, 13 June 2015

Building a Tour with Hugin and Pannellum - Part III - Publishing

Quick Reference

  1. The software
  2. Convert the panorama to cubic
  3. Edit the floor
  4. Create the panorama
  5. Create the tour

The Software

I have found that Pannellum offers an excellent solution to presenting the panorama online:  It is open source and free; It does not require flash or java plugins; It is small; and it can be modified as required.

There are, however, a couple of tweaks I have made in order to better perform the processing:

  • Modification to the generate.py script to allow a two-stage generation (panorama to cube, and cube to multiview) - this gives you a stage where you can edit the floor; and to automatically generate test html pages.
  • Modification to the pannellum.htm renderer to allow it to run on a local filesystem for testing; and to fix some bearings bugs.
  • It should be noted that the pannellum is in development, and at the point of writing, a modification is being made to allow all locations to be oriented north-south, so you don't have to mess around with re-orienting the viewer when they enter each scene.
I will have a different blog entry for the modifications, after I can see which ones can be submitted to Matthew Petroff, the original application developer himself.

Convert the panorama to cubic

To convert the panorama to a cubic view, you will need to run the command-line script 'generate.py'.  All you need to do is pass it the source image, and the target directory (and tell it to just produce the cubic view).  
python generate.py PanoramaDirectory --r2c InputImage.tif
Each of the views is named facexxxxx.tif.


Edit the floor


This is where you can load the downward shot into your favorite image editor (I use Gimp) and fix the hole!

This is where you will also appreciate that you thought about where you put the tripod.

There are several techniques you can use:
  • Stretch the downward shot you did (without the tripod) and merge it in place.
  • Copy some other parts of the image, stretch and rotate as required to fill the gap.
  • Put a circular image of your own there.
  • Go back to the "Changing the Orientation" stage in Hugin (Part II) and in the previewer change the projection tab field of view to 360, and lens to fisheye, then on the move tab, drag the tripod hole to the centre of the image, export the image, edit in Gimp to remove the tripod hole, and then use the resulting circular image as the plug.


Create the panorama

Creating the panorama is easy, you just need to run the second part of the generate.py script:
python generate.py PanoramaDirectory --c2mv InputImage.tif

The panorama for the image will be created in the PanoramaDirectory.  At this stage, I copy the whole directory into the place where I am building my website, and I remove the facexxxx.tif and the .pto files, as they are only used in the creation of the tour, not the tour itself.

Each tour set has a sample config.json file, which you can use as part of your tour, e.g.:
{
    "type": "multires",
   
    "multiRes": {
        "path": "./%l/%s%y_%x",
        "fallbackPath": "./fallback/%s",
        "extension": "jpg",
        "tileResolution": 512,
        "maxLevel": 4,
        "cubeResolution": 3816
    }
}

Create the tour

This is probably one of the most time consuming parts, because the way panellum works is it loads the tour into a single (json) file.  If you follow these prescriptive rules, however, you should be OK.

I organise my tour folder structure as follows:
tour/
    pannellum.htm
    index.html
    config.json

    scene1/
    scene2/


pannellum.htm - This is the main pannellum processing engine - you can download the latest copy from pannellum.org.  You can also download and build from source if you want to fix, modify or tweak.

index.html - This is your webpage, which is used to pull everything together.  I use Cascaded Stylesheet, divs and classes to force the presentation (see here for the working example), but the simplified version is here:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="fr" xml:lang="en">

<head>
    <title>Tour Example - blog.trumpton.org.uk</title>
    <meta  charset="utf-8" />
</head>

<body>   
    <div id="widecontent">
        <iframe title="Tour" class="tour"
            webkitAllowFullScreen mozallowfullscreen allowFullScreen 
            src="pannellum.htm?tour=cambo.json">
        </iframe>
    </div>

</body>
</html>
config.json - This is the file where your entire tour is defined.  The file is split into 2 sections: The defaults and the tour, and the tour is split into scenes, each of which is split into 2 parts - the definition of the scene, and the hotspots to link to other scenes.  This is a cut-down example of my Guresekertua tour:

Start the json file:
{
The Default Section, including the first scene to use, and the pitch and yaw to use for the first view.  In this example, hotSpotDebug is set to true, which is useful when setting hotspots:
   "default": {
        "author": "Steve Clarke",
        "firstScene": "terrasse1",
        "pitch": 3,
        "yaw": 180,
        "sceneFadeDuration": 2000,
        "compass": true,
        "autoLoad": true,
        "autoRotate": true,
        "autoRotateInactivityDelay": 10000,
        "hotSpotDebug": true
    },

The start of the scenes:
    "scenes": {
The first scene (terrasse1), the adjustment to make the scene align due north (should not be needed if you aligned your photos when you took them), its title and scene information.

Note that the multiRes section can be copied from the config.json example in the appropriate scene directory, however, you need to include the basePath, and double-check the dots and slashes in the path and fallbackPath entries.

        "terrasse1": {
            "northOffset": -2,
            "title": "Gure Sekretua Terrasse",
            "preview": "./tour-cover.jpg",
            "type": "multires",
            "multiRes": {
                "basePath": "./Terrasse1/",
                "path": "./%l/%s%y_%x",
                "fallbackPath": "/fallback/%s",
                "extension": "jpg",
                "tileResolution": 512,
                "maxLevel": 3,
                "cubeResolution": 2048
            },
 The definition of the hotSpots (links to other scenes).  You can also have information points, and links to stills and videos, but those are not discussed in any detail here.

Each hotspot has a pitch and yaw (location of the hotspot in the current scene).  To work out what they should be, load the scene up in a web browser, enable the debugger (e.g. Control-Shift-I) and click on a point and read off the numbers.

The sceneId, targetPitch and targetYaw define where the hotspot links to.  If the targetYaw is "same", the orientation of the view is maintained when you enter the scene (no need to calculate), but this does necessitate that all scenes are correctly oriented north/south.

At the time of writing, if you enter a numerical targetYaw here, it does not take into consideration the northOffset of the target scene, so you have to do some subtractions for any yaw value you use.  I suspect this will be fixed in future releases.
            "hotSpots": [{
                "pitch": -1,
                "yaw": 86,
                "type": "scene",
                "text": "Chemin",
                "sceneId": "chemin",
                "targetPitch": -5,
                "targetYaw": "same"
            }, {
                "yaw": 167,
                "pitch": 2,
                "type": "scene",
                "text": "Appartement",
                "sceneId": "sejour1",
                "targetYaw": 178,
                "targetPitch": -5
           }, {
                "pitch": -5,
                "yaw": 48 ,
                "type": "info",
                "text": "La Gare et Bas Cambo"
            }]
        },
Other Scenes follow (note a comma does not follow the final scene):


        "terrasse2": {
            ....

        },

"sejour1": {
    ....

}
Finally, the file is closed
    }
}
When you've generated the file, you can test it.  Recommendation is to have the debug open in the web viewer, and make use of a json syntax validator if you need (see debugging, below).

Hints

Any of the parameters in the "default" section of the json file can be over-ridden in the html file which calls up the tour.  This means that, for example, the same tour files can be used, but different 'starting places' can be selected.
<iframe title="Frame Title"
  class="tour"
  webkitAllowFullScreen
  mozallowfullscreen
  allowFullScreen  
  src="pannellum.htm?tour=config.json&firstScene=sejour1"> 
</iframe>

Debugging

Running a Webserver

Now, it should not be possible to run the website from the local filesystem due to browser security restrictions (although I have found that these do not exist in Firefox, and Firefox allows you to access data through 'file:' references).  It has been hard-coded into pannellum to deny you access to local files from the pannellum.htm script, so you will need to run a webserver, or upload your files to a webserver to try.

Running a webserver is easy if you have python installed (which you probably will have if you have used generate.py earlier!).

Create a script to launch a webserver, then connect to it with "http://localhost:8000/" - I use Google Chrome.

#!/bin/sh
cd <path>/<to>/<your>/<local>/<website>/<image>
python -m SimpleHTTPServer

Running the Debugger

Turning on the debugger (Control-Shift-I in Google Chrome) is a useful way of getting things sorted.  Some example messages, errors and fixes are shown below:

hotSpotDebug

This shows the Pitch and Yaw of the mouse-click, and the centre of the image.  It needs "hotSpotDebug": true in the config.json file, or &hotSpotDebug=true in the URL used to load the tour - in the debugger, you will get messages such as this, everytime you click the mouse:
Pitch: 3.6160727325047777, Yaw: 184.96700450304274, Center Pitch: 3, Center Yaw: 169.18935205326287, HFOV: 100

Page Blank / Error Messages

If your page isn't showing, it could be due to bugs in your configuration.  To check the configuration is correctly formatted, you can use a context-sensitive editor (I use gedit on Ubuntu), or paste your code into an analyser / checker.

Example messages in the debugger / display:
Rogue '{' in the json file:
Uncaught SyntaxError: Unexpected token (r.config.M.onload @ pannellum.htm?tour=config.json&firstScene=scenename

Missing json file:

GET http://localhost:8000/config.json 404 (File not found)parseURLParameters @ pannellum.htm?tour=config.json

Incorrect firstScene reference name, either in the json file, or in the URL:
No panorama image was specified


My finished product can be found here.

6 comments:

  1. Hi,
    This is just what I was searching :-). Thanks for infos, i'll try to make my multires pannellum tour now.

    ReplyDelete
  2. Indeed, thank you, very helpful, especially the debugging of hot spots.

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete
  4. You mentioned a description how to convert a panorama to cubic via "python generate.py PanoramaDirectory --r2c InputImage.tif"

    But generate.py (from pannellum 2.3.2) doesn't know the parameter --r2c.

    Any suggestions?

    ReplyDelete
    Replies
    1. I contacted the author, who did not believe that this function was necessary. Generate.py does go through these two steps, but doesn't allow you to stop part-way. That's why I ended up modifying the code to allow you to break-out with --r2c and --c2mv.

      Delete
  5. Very useful article....will surely follow it thoroughly

    ReplyDelete