Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ibm
GitHub Repository: ibm/watson-machine-learning-samples
Path: blob/master/cloud/notebooks/python_sdk/deployments/python_scripts/Use script to recognize hand-written digits.ipynb
9212 views
Kernel: .venv_watsonx_ai_samples_py_312

image

Use Python script to recognize hand-written digits with ibm-watsonx-ai

Disclaimers

  • Use only Projects and Spaces that are available in watsonx context.

Notebook content

Create and deploy a script that receives HTML canvas image data from a web app and then processes and sends that data to a model trained to recognize handwritten digits. See: MNIST function deployment tutorial

Some familiarity with Python is helpful. This notebook uses Python 3.12.

Learning goals

The learning goals of this notebook are:

  • Create and save a Python script.

  • Deploy the script using the client library.

  • Create and run a job which utilizes the created deployment.

Contents

This notebook contains the following parts:

  1. Setup

  2. Get an ID for a model deployment

  3. Get sample canvas data

  4. Python Script Deployment

  5. Create and Run job

  6. Clean up

  7. Summary

Set up the environment

Before you use the sample code in this notebook, you must perform the following setup tasks:

Install dependencies

Note: ibm-watsonx-ai documentation can be found here.

%pip install -U wget | tail -n 1 %pip install -U matplotlib | tail -n 1 %pip install -U ibm-watsonx-ai | tail -n 1
Successfully installed wget-3.2 Successfully installed contourpy-1.3.3 cycler-0.12.1 fonttools-4.61.1 kiwisolver-1.4.9 matplotlib-3.10.8 numpy-2.4.1 pillow-12.1.0 pyparsing-3.3.1 Successfully installed anyio-4.12.1 cachetools-6.2.4 certifi-2026.1.4 charset_normalizer-3.4.4 h11-0.16.0 httpcore-1.0.9 httpx-0.28.1 ibm-cos-sdk-2.14.3 ibm-cos-sdk-core-2.14.3 ibm-cos-sdk-s3transfer-2.14.3 ibm-watsonx-ai-1.5.0 idna-3.11 jmespath-1.0.1 lomond-0.3.3 pandas-2.2.3 pytz-2025.2 requests-2.32.5 tabulate-0.9.0 typing_extensions-4.15.0 tzdata-2025.3 urllib3-2.6.3

Connection to watsonx.ai Runtime

Authenticate the watsonx.ai Runtime service on IBM Cloud. You need to provide platform api_key and instance location.

You can use IBM Cloud CLI to retrieve platform API Key and instance location.

API Key can be generated in the following way:

ibmcloud login ibmcloud iam api-key-create API_KEY_NAME

In result, get the value of api_key from the output.

Location of your watsonx.ai Runtime instance can be retrieved in the following way:

ibmcloud login --apikey API_KEY -a https://cloud.ibm.com ibmcloud resource service-instance INSTANCE_NAME

In result, get the value of location from the output.

Tip: Your Cloud API key can be generated by going to the Users section of the Cloud console. From that page, click your name, scroll down to the API Keys section, and click Create an IBM Cloud API key. Give your key a name and click Create, then copy the created key and paste it below. You can also get a service specific url by going to the Endpoint URLs section of the watsonx.ai Runtime docs. You can check your instance location in your watsonx.ai Runtime Service instance details.

You can also get service specific apikey by going to the Service IDs section of the Cloud Console. From that page, click Create, then copy the created key and paste it below.

Action: Enter your api_key and location in the following cell.

import getpass from ibm_watsonx_ai import Credentials api_key = getpass.getpass("Please enter your watsonx.ai api key (hit enter): ") credentials = Credentials( url="https://us-south.ml.cloud.ibm.com", api_key=api_key, )

Working with spaces

First of all, you need to create a space that will be used for your work. If you do not have space already created, you can use Deployment Spaces Dashboard to create one.

  • Click New Deployment Space

  • Create an empty space

  • Select Cloud Object Storage

  • Select watsonx.ai Runtime instance and press Create

  • Copy space_id and paste it below

Tip: You can also use SDK to prepare the space for your work. More information can be found here.

Action: Assign space ID below

import os try: space_id = os.environ["SPACE_ID"] except KeyError: space_id = input("Please enter your space_id (hit enter): ")

Create APIClient instance

from ibm_watsonx_ai import APIClient client = APIClient(credentials=credentials, space_id=space_id)

Get an ID for a model deployment

The deployed function created in this notebook is designed to send payload data to a TensorFlow model created in the MNIST tutorials.

Option 1: Use your own, existing model deployment

If you already deployed a model while working through one of the following MNIST tutorials, you can use that model deployment:

Paste the model deployment ID in the following cell.

See: Looking up an online deployment ID

model_deployment_id = input("Please enter your model deployment ID (hit enter to skip)")

Option 2: Download, store, and deploy a sample model

You can deployed a sample model and get its deployment ID by running the code in the following four cells.

import wget sample_saved_model_filename = "mnist-tf-model.tar.gz" url = f"https://github.com/IBM/watsonx-ai-samples/raw/master/cloud/models/tensorflow/mnist/{sample_saved_model_filename}" if not os.path.isfile(sample_saved_model_filename): wget.download(url)

Look up software specification for the MNIST model

software_spec_id = client.software_specifications.get_id_by_name("runtime-25.1-py3.12")

Store the sample model in your watsonx.ai Runtime repository

metadata = { client.repository.ModelMetaNames.NAME: "Saved MNIST model", client.repository.ModelMetaNames.TYPE: "tensorflow_2.18", client.repository.ModelMetaNames.SOFTWARE_SPEC_ID: software_spec_id, } model_details = client.repository.store_model( model=sample_saved_model_filename, meta_props=metadata )

Get published model ID

published_model_id = client.repository.get_model_id(model_details)

Deploy the stored model

metadata = { client.deployments.ConfigurationMetaNames.NAME: "MNIST saved model deployment", client.deployments.ConfigurationMetaNames.ONLINE: {}, } model_deployment_details = client.deployments.create( published_model_id, meta_props=metadata )
###################################################################################### Synchronous deployment creation for id: 'b60ad635-46d8-41cd-99b7-c85db2df6b3c' started ###################################################################################### initializing Note: online_url and serving_urls are deprecated and will be removed in a future release. Use inference instead. ...... ready ----------------------------------------------------------------------------------------------- Successfully finished deployment creation, deployment_id='23f7cc26-96ac-4ab8-84dd-04dd56654d50' -----------------------------------------------------------------------------------------------

Get the ID of the model deployment just created

model_deployment_id = client.deployments.get_id(model_deployment_details) print(model_deployment_id)
23f7cc26-96ac-4ab8-84dd-04dd56654d50

Get sample canvas data

The deployed function created in this notebook is designed to accept RGBA image data from an HTML canvas object.

Run the following cells to download and view sample canvas data for testing the deployed function.

Download sample data file

Download the file containing the sample data

sample_canvas_data_filename = "mnist-html-canvas-image-data.json" url = f"https://github.com/IBM/watsonx-ai-samples/raw/master/cloud/data/mnist/{sample_canvas_data_filename}" if not os.path.isfile(sample_canvas_data_filename): wget.download(url)

Load the sample data from the file into a variable

import json with open(sample_canvas_data_filename) as data_file: sample_canvas_data = json.load(data_file)

View sample data

View the raw contents of the sample data

print("Height (n): " + str(sample_canvas_data["height"]) + " pixels\n") print( "Num image data entries: " + str(len(sample_canvas_data["data"])) + " - (n * n * 4) elements - RGBA values\n" ) print( json.dumps(sample_canvas_data, indent=3)[:75] + "...\n" + json.dumps(sample_canvas_data, indent=3)[-50:] )
Height (n): 187 pixels Num image data entries: 139876 - (n * n * 4) elements - RGBA values { "data": [ 0, 0, 0, 0, 0, 0, ... 0, 0, 0 ], "height": 187 }

See what hand-drawn digit the sample data represents

import matplotlib.pyplot as plt import numpy as np rgba_arr = np.asarray(sample_canvas_data["data"]).astype("uint8") n = sample_canvas_data["height"] plt.figure() plt.imshow(rgba_arr.reshape(n, n, 4)) plt.xticks([]) plt.yticks([]) plt.show()
Image in a Jupyter notebook

Python Script Deployment

Save Python Script

This file will be saved locally so you can deploy and run it later.

%%writefile /tmp/script.py import os import json import traceback import numpy as np from PIL import Image from ibm_watsonx_ai import APIClient, Credentials JOBS_PAYLOAD_FILE = os.getenv("JOBS_PAYLOAD_FILE", "") BATCH_INPUT_DIR = os.getenv("BATCH_INPUT_DIR", "") BATCH_OUTPUT_DIR = os.getenv("BATCH_OUTPUT_DIR", "") API_KEY = os.getenv("API_KEY", "") LOCATION = os.getenv("LOCATION", "") SPACE_ID = os.getenv("SPACE_ID", "") MODEL_DEPLOYMENT_ID = os.getenv("MODEL_DEPLOYMENT_ID", "") if not any( ( JOBS_PAYLOAD_FILE, BATCH_INPUT_DIR, BATCH_OUTPUT_DIR, API_KEY, LOCATION, SPACE_ID, MODEL_DEPLOYMENT_ID, ) ): exit(1) def get_image_array(canvas_data): rgba_data, dimension = canvas_data rgba_array = np.asarray(rgba_data).astype("uint8") return rgba_array.reshape(dimension, dimension, 4) def main(): try: with open(JOBS_PAYLOAD_FILE, "r") as fp: data = json.load(fp) function_payload = data["scoring"] # Read the payload received by the function canvas_data = function_payload["input_data"][0]["values"] # Create an array object with the required shape image_array = get_image_array(canvas_data) # Create an image object that can be resized img = Image.fromarray(image_array, "RGBA") # Resize the image to 28 x 28 pixels sm_img = img.resize((28, 28), Image.LANCZOS) # Get alpha channel of resized image alpha_arr = np.array(sm_img.split()[-1]).reshape((28, 28, 1)) # Create a payload to be sent to the model model_payload = {"input_data": [{"values": [alpha_arr]}]} credentials = Credentials( api_key=API_KEY, url="https://" + LOCATION + ".ml.cloud.ibm.com" ) client = APIClient(credentials, space_id=SPACE_ID) model_result = client.deployments.score(MODEL_DEPLOYMENT_ID, model_payload) with open(os.path.join(BATCH_OUTPUT_DIR, "output.json"), "w") as fp: json.dump(model_result, fp) return model_result except Exception: tb = traceback.format_exc() with open(os.path.join(BATCH_OUTPUT_DIR, "output.json"), "w") as fp: json.dump({"predictions": [{"values": [tb]}]}, fp) if __name__ == "__main__": main()
Writing /tmp/script.py

The file should be successfully created. To check its content, you can use the command below.

from IPython.display import Markdown, display with open("/tmp/script.py", "r") as f: code_content = f.read() display(Markdown(f"```python\n{code_content}\n```"))
import os import json import traceback import numpy as np from PIL import Image from ibm_watsonx_ai import APIClient, Credentials JOBS_PAYLOAD_FILE = os.getenv("JOBS_PAYLOAD_FILE", "") BATCH_INPUT_DIR = os.getenv("BATCH_INPUT_DIR", "") BATCH_OUTPUT_DIR = os.getenv("BATCH_OUTPUT_DIR", "") API_KEY = os.getenv("API_KEY", "") LOCATION = os.getenv("LOCATION", "") SPACE_ID = os.getenv("SPACE_ID", "") MODEL_DEPLOYMENT_ID = os.getenv("MODEL_DEPLOYMENT_ID", "") if not any( ( JOBS_PAYLOAD_FILE, BATCH_INPUT_DIR, BATCH_OUTPUT_DIR, API_KEY, LOCATION, SPACE_ID, MODEL_DEPLOYMENT_ID, ) ): exit(1) def get_image_array(canvas_data): rgba_data, dimension = canvas_data rgba_array = np.asarray(rgba_data).astype("uint8") return rgba_array.reshape(dimension, dimension, 4) def main(): try: with open(JOBS_PAYLOAD_FILE, "r") as fp: data = json.load(fp) function_payload = data["scoring"] # Read the payload received by the function canvas_data = function_payload["input_data"][0]["values"] # Create an array object with the required shape image_array = get_image_array(canvas_data) # Create an image object that can be resized img = Image.fromarray(image_array, "RGBA") # Resize the image to 28 x 28 pixels sm_img = img.resize((28, 28), Image.LANCZOS) # Get alpha channel of resized image alpha_arr = np.array(sm_img.split()[-1]).reshape((28, 28, 1)) # Create a payload to be sent to the model model_payload = {"input_data": [{"values": [alpha_arr]}]} credentials = Credentials( api_key=API_KEY, url="https://" + LOCATION + ".ml.cloud.ibm.com" ) client = APIClient(credentials, space_id=SPACE_ID) model_result = client.deployments.score(MODEL_DEPLOYMENT_ID, model_payload) with open(os.path.join(BATCH_OUTPUT_DIR, "output.json"), "w") as fp: json.dump(model_result, fp) return model_result except Exception: tb = traceback.format_exc() with open(os.path.join(BATCH_OUTPUT_DIR, "output.json"), "w") as fp: json.dump({"predictions": [{"values": [tb]}]}, fp) if __name__ == "__main__": main()

Deployment of Python Script

You can store and deploy a Python script and get its details by running the code in following cells.

sw_spec_id = client.software_specifications.get_uid_by_name("runtime-25.1-py3.12") sw_spec_id
'f47ae1c3-198e-5718-b59d-2ea471561e9e'
meta_props = { client.script.ConfigurationMetaNames.NAME: "Python script", client.script.ConfigurationMetaNames.SOFTWARE_SPEC_UID: sw_spec_id, }
script_path = "/tmp/script.py" script_details = client.script.store(meta_props, file_path=script_path) script_id = client.script.get_id(script_details) print("Created script ", script_id)
Creating Script asset... SUCCESS Created script c5aff193-51bb-486b-873a-55bf73160e02
deployment_meta_props = { client.deployments.ConfigurationMetaNames.NAME: "pyscript_deployment", client.deployments.ConfigurationMetaNames.BATCH: {}, client.deployments.ConfigurationMetaNames.HARDWARE_SPEC: {"name": "S"}, } deployment_details = client.deployments.create(script_id, deployment_meta_props) deployment_id = client.deployments.get_id(deployment_details)
###################################################################################### Synchronous deployment creation for id: 'c5aff193-51bb-486b-873a-55bf73160e02' started ###################################################################################### ready. ----------------------------------------------------------------------------------------------- Successfully finished deployment creation, deployment_id='c978447b-9fea-4191-b3ed-748a722f14d2' -----------------------------------------------------------------------------------------------

Create and Run job

Run the following cells to create and run a job with the deployed script.

import time def poll_async_job(client: APIClient, job_uid: str): while True: job_status = client.deployments.get_job_status(job_uid) print(job_status) state = job_status["state"] if state == "completed" or "fail" in state: return client.deployments.get_job_details(job_uid) time.sleep(5)
job_payload_ref = { client.deployments.ScoringMetaNames.INPUT_DATA: [ { "fields": ["data", "height"], "values": [sample_canvas_data["data"], sample_canvas_data["height"]], } ], client.deployments.ScoringMetaNames.OUTPUT_DATA_REFERENCE: { "type": "data_asset", "location": {"name": "deploy_test_script-out"}, }, client.deployments.ScoringMetaNames.ENVIRONMENT_VARIABLES: { "API_KEY": api_key, "LOCATION": "us-south", "SPACE_ID": space_id, "MODEL_DEPLOYMENT_ID": model_deployment_id, }, } job = client.deployments.create_job(deployment_id, meta_props=job_payload_ref) job_id = client.deployments.get_job_id(job)
job_details = poll_async_job(client, job_id)
{'completed_at': '', 'running_at': '', 'state': 'queued'} {'completed_at': '', 'running_at': '', 'state': 'queued'} {'completed_at': '', 'running_at': '', 'state': 'queued'} {'completed_at': '', 'running_at': '', 'state': 'queued'} {'completed_at': '', 'running_at': '', 'state': 'running'} {'completed_at': '', 'running_at': '', 'state': 'running'} {'completed_at': '2026-01-16T15:14:22.703522Z', 'running_at': '2026-01-16T15:14:08.195712Z', 'state': 'running'} {'completed_at': '2026-01-16T15:14:22.703522Z', 'running_at': '2026-01-16T15:14:08.195712Z', 'state': 'completed'}
client.data_assets.list().iloc[-1]
NAME deploy_test_script-out ASSET_TYPE data_asset SIZE 278 ASSET_ID 754e20d5-6a45-4eb3-8f43-ccad7cab2fa8 Name: 0, dtype: object

To download the asset run the code below. It will be downloaded as a zip archive.

from ibm_watsonx_ai.helpers import DataConnection scoring_params = client.deployments.get_job_details(job_id) output_data_connection = DataConnection.from_dict( scoring_params["entity"]["scoring"]["output_data_reference"] ) output_data_connection.set_client(client) output_data_connection.download("/tmp/script_result.json")
Collecting pyarrow>=3.0.0 Using cached pyarrow-22.0.0-cp312-cp312-macosx_12_0_arm64.whl.metadata (3.2 kB) Using cached pyarrow-22.0.0-cp312-cp312-macosx_12_0_arm64.whl (34.2 MB) Installing collected packages: pyarrow Successfully installed pyarrow-22.0.0
with open("/tmp/script_result.json") as fp: result = json.load(fp) print(result)
{'predictions': [{'id': 'output_0', 'values': [[2.65692210632551e-06, 0.0010547457495704293, 3.4101554774679244e-05, 1.9494355001370423e-06, 0.9868835806846619, 0.00032408724655397236, 0.005543855484575033, 0.0006548759411089122, 0.004688874818384647, 0.0008112657815217972]]}]}

Cleanup

If you want to clean up all created assets:

  • experiments

  • trainings

  • pipelines

  • model definitions

  • models

  • functions

  • deployments

please follow up this sample notebook.

Summary and next steps

You successfully completed this notebook!

You created a Python script that receives HTML canvas image data and then processes and sends that data to a model trained to recognize handwritten digits.

Check out our Online Documentation for more samples, tutorials, documentation, how-tos, and blog posts.

Author

Jakub Owczarek, Software Engineer at watsonx.ai

Copyright © 2025-2026 IBM. This notebook and its source code are released under the terms of the MIT License.