RICOH360 Viewer Tutorial
This tutorial takes you from a blank file to the point where you can display an image in a web browser.
There are two components: front-end and back-end.
component | name | function |
---|---|---|
front-end | index.html | load RICOH360 Viewer |
back-end | server.py | send content to viewer and handle security |
The suggested way to use this tutorial is to start with a blank file using an editor like VSCode. Type in all the code snippets one section at a time. For each section, take a moment to understand the functionality. For example, one section is on token creation for the RICOH360 Viewer. Another section is on token creation for the RICOH360 Cloud API. The two tokens are different and use different token generation technologies.
If you get stuck, you can refer to the completed code on GitHub or copy and paste the snippet into your own application.
Tutorial code on GitHub
front-end HTML File
Create a barebones HTML file called index.html
.
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
</body>
</html>
Insert the viewer in the head.
<script src="https://r360pf-prod-static.s3.us-west-2.amazonaws.com/viewer/v0.15.0/ricoh360-viewer.js"></script>
In the body, create a div with id="viewer"
.
<div
style="
position: relative;
width: 100%;
height: 650px;
background-color: #2a303c;
"
>
<div id="viewer"></div>
</div>
instantiate viewer
In the snippet below, we will pass the token into the index.html
from
Flask.
<script>
const viewer = new RICOH360Viewer({
divId: "viewer",
onFetchToken: () => "{{token}}",
});
</script>
At this stage, the index.html
file looks like this.
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://r360pf-prod-static.s3.us-west-2.amazonaws.com/viewer/v0.15.0/ricoh360-viewer.js"></script>
</head>
<body>
<div
style="
position: relative;
width: 100%;
height: 650px;
background-color: #2a303c;
"
>
<div id="viewer"></div>
</div>
<script>
const viewer = new RICOH360Viewer({
divId: "viewer",
onFetchToken: () => "{{token}}",
});
</script>
</body>
</html>
The viewer can't be instantiated because it needs a token.
secrets.env
Using the following information obtained from RICOH, create
a secrets.env
file.
- Client ID
- Client Secrete
- Private Key
back-end Python file
Create a new python file in the same directory called server.py
in the
same directory as the index.html
file.
Create virtual environment.
python -m venv venv
Activate virtual environment.
source venv/bin/activate
The file structure will look like this.
Install dependencies.
pip install PyJWT Flask python-dotenv requests
Add skeleton flask server code.
from flask import Flask, render_template
import jwt
import os
from dotenv import load_dotenv
load_dotenv("secrets.env")
app = Flask(__name__)
app.template_folder = "."
# Retrieve environment variables
PRIVATE_KEY = os.getenv("PRIVATE_KEY")
CLIENT_ID = os.getenv("CLIENT_ID")
CLIENT_SECRET = os.getenv("CLIENT_SECRET")
@app.route("/")
def index():
return render_template("index.html")
if __name__ == "__main__":
app.run(port=3000, debug=True)
print("Open browser at http://localhost:3000 or http://127.0.0.1:3000")
At this stage, if you open the browser, you will just have a black screen. You won't be able to see the image because you still need to
pass the viewer the token
and the contentId
You can test the server with:
python server.py
* Serving Flask app 'server'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:3000
Press CTRL+C to quit
creating the token for the RICOH360 Viewer
Install cryptography
to generate token.
pip install cryptography
Create the method create_token
above the route.
def create_token():
payload = {"client_id": CLIENT_ID}
token = jwt.encode(payload, PRIVATE_KEY, algorithm="RS256")
# Decode to UTF-8 if necessary
return token if isinstance(token, str) else token.decode("utf-8")
Pass the token to the HTML page.
@app.route("/")
def index():
token = create_token()
return render_template("index.html", token=token)
Run the server and test it again to make sure the token is being created.
token display challenge
As a challenge, display the token on your HTML page using this syntax {{name_of_token_variable}}
accessing content
To get the content, you first need to generate a token for the content using Amazon Cognito.
You then use the content token to query the RICOH Cloud API.
First, install Python requests
to make an HTTP call.
pip install requests
To make the request to Cognito, you need to import the base64
package.
Your imports will look like this:
Generating RICOH360 Cloud Content Token with Amazon Cognito
First, use the method below to create a token for the RICOH60 Cloud.
def get_token_for_cloud_content():
# Endpoint and authentication for AWS token
token_endpoint = "https://saas-prod.auth.us-west-2.amazoncognito.com/oauth2/token" # noqa: E501
auth = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode("utf-8") # noqa: E501
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": f"Basic {auth}",
}
body = {"grant_type": "client_credentials", "scope": "all/read"}
# Request AWS token
token_response = requests.post(token_endpoint, headers=headers, data=body)
token_object = token_response.json()
ricoh_cloud_access_token = token_object.get("access_token")
return ricoh_cloud_access_token
Send Request to RICOH360 Cloud API
With the token for the Cloud API, you can now send a request for content.
Query content from the RICOH360 API
The code below will get a single piece of content from the RICOH Cloud API.
def get_content():
cloud_content_token = get_token_for_cloud_content()
# Fetch content using the token
content_headers = {"Authorization": f"Bearer {cloud_content_token}"}
content_response = requests.get(
"https://api.ricoh360.com/contents?limit=1", headers=content_headers
)
content_data = content_response.json()
return content_data
parse content_id and send to HTML template
In the return line, make sure you had contentId=contentId
to the
variables sent to the index.html
file.
@app.route("/")
def index():
token = create_token()
content_data = get_content()
contentId = content_data[0]["content_id"]
print(f"contentId: {contentId}")
return render_template("index.html", token=token, contentId=contentId)
completed back-end Python code listing
from flask import Flask, render_template
import jwt
import os
from dotenv import load_dotenv
import requests
import base64
load_dotenv("secrets.env")
app = Flask(__name__)
app.template_folder = "views"
app.static_folder = "public"
# Retrieve environment variables
PRIVATE_KEY = os.getenv("PRIVATE_KEY")
CLIENT_ID = os.getenv("CLIENT_ID")
CLIENT_SECRET = os.getenv("CLIENT_SECRET")
def create_token():
payload = {"client_id": CLIENT_ID}
token = jwt.encode(payload, PRIVATE_KEY, algorithm="RS256")
# Decode to UTF-8 if necessary
return token if isinstance(token, str) else token.decode("utf-8")
def get_token_for_cloud_content():
# Endpoint and authentication for AWS token
token_endpoint = "https://saas-prod.auth.us-west-2.amazoncognito.com/oauth2/token" # noqa: E501
auth = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode("utf-8") # noqa: E501
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": f"Basic {auth}",
}
body = {"grant_type": "client_credentials", "scope": "all/read"}
# Request AWS token
token_response = requests.post(token_endpoint, headers=headers, data=body)
token_object = token_response.json()
ricoh_cloud_access_token = token_object.get("access_token")
return ricoh_cloud_access_token
# Function to query content from the RICOH360 API
def get_content():
cloud_content_token = get_token_for_cloud_content()
# Fetch content using the token
content_headers = {"Authorization": f"Bearer {cloud_content_token}"}
content_response = requests.get(
"https://api.ricoh360.com/contents?limit=1", headers=content_headers
)
content_data = content_response.json()
return content_data
@app.route("/")
def index():
token = create_token()
content_data = get_content()
contentId = content_data[0]["content_id"]
print(f"contentId: {contentId}")
return render_template("index.html", token=token, contentId=contentId)
if __name__ == "__main__":
app.run(port=3000, debug=True)
print("Open browser at http://localhost:3000 or http://127.0.0.1:3000")
front-end: display content in viewer
In index.html
.
completed front-end index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://r360pf-prod-static.s3.us-west-2.amazonaws.com/viewer/v0.15.0/ricoh360-viewer.js"></script>
</head>
<body>
<div
style="
position: relative;
width: 100%;
height: 650px;
background-color: #2a303c;
"
>
<div id="viewer"></div>
</div>
<script>
const viewer = new RICOH360Viewer({
divId: "viewer",
onFetchToken: () => "{{token}}",
});
viewer.start({
contentId: "{{contentId}}"
});
</script>
</body>
</html>
Additional Challenges
insert additional UI buttons in the viewer
Tip: Add the ui object into the viewer at time of instantiation.
The split pane button will work.
Solution is in the tutorial folder in button-challenge.html
.
simple list of content
We're passing a single image contentId into the HTML. Modifer the code to show a simple list.
In the server Python file
# Function to query content from the RICOH360 API
def get_content():
cloud_content_token = get_token_for_cloud_content()
# Fetch content using the token
content_headers = {"Authorization": f"Bearer {cloud_content_token}"}
content_response = requests.get(
"https://api.ricoh360.com/contents?limit=10", headers=content_headers
)
content_data = content_response.json()
return content_data
@app.route("/")
def index():
token = create_token()
content_data = get_content()
contentIds = []
for single_content in content_data:
contentIds.append(single_content["content_id"])
return render_template("simple-list-challenge.html", token=token, contentIds=contentIds)
in the HTML file
...
...
viewer.start({
contentId: "{{contentIds[0]}}"
});
</script>
{% for contentId in contentIds %}
<button onclick="viewer.switchScene({ contentId: '{{contentId}}'})"> {{ loop.index }}</button>
{% endfor %}
Solution in the tutorial folder on GitHub.
content list as thumbnail with enhancement
The content from the RICOH360 Cloud has a URL for a thumbnail. The thumbnails are useful to select images.
Modify the Python server to send a list of thumbnail URLs to the HTML file.
@app.route("/")
def index():
token = create_token()
content_data = get_content()
thumburls = []
contentIds = []
for single_content in content_data:
contentIds.append(single_content["content_id"])
thumburls.append(single_content["thumbnail_url"])
return render_template("list-with-transform.html",
token=token,
contentIds=contentIds,
thumburls=thumburls
)
In the HTML file, the listing is built up from the thumbnail URLs.
{% for contentId in contentIds %}
<img onclick="switchViewerImage('{{contentId}}')" src="{{thumburls[loop.index -1]}}" alt="thumbnail"
style="width:80px;height: 40px;">
{% endfor %}
Example to switch the scene.
const switchViewerImage = async (contentId) => {
document.getElementById("currentContentId").innerHTML = contentId;
const buttonElement = `<button onclick="enhanceRightImage('${contentId}')"> enhance right </button>`;
document.getElementById("rightEnhanceButton").innerHTML = buttonElement;
const switchRightButtonElement = `<button onclick="switchRightImage('${contentId}')"> sync right </button>`;
document.getElementById("rightSwitchButton").innerHTML = switchRightButtonElement;
console.log("contentId for left pane: ", contentId);
await viewer.switchScene(
{
contentId: contentId,
}
);
}
Full working example in the tutorial folder.