Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue #29: Tagarno API documentation and API integration #54

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ NACHET_BLOB_PIPELINE_DECRYPTION_KEY=
NACHET_MAX_CONTENT_LENGTH=
NACHET_VALID_EXTENSION=
NACHET_VALID_DIMENSION=
NACHET_SUBSCRIPTION_ID=
NACHET_RESOURCE_GROUP=
NACHET_WORKSPACE=
NACHET_MODEL=
MICROSCOPE_URL=
METHODS=
5 changes: 5 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"recommendations": [
"stkb.rewrap"
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/asssets/image/camera_properties.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/asssets/image/file_properties.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/asssets/image/image_properties.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/asssets/image/origin_properties.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
169 changes: 169 additions & 0 deletions docs/tagarno-api-objective.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Tagarno API Objective

## Main objective for milestones 1

The main objective of the Tagarno API integration is to be able to retrieve data
from the image taken by the microscope to build a trust threshold. For example,
we want to build confidence level, base on the condition and configuration a
picture was taken. Having data on this will help build a confidence level in
Nachet prediction.

### Opportunity Tagarno API

---
Tagarno is offering an API that can retrieve and set the config of the
microscope. However, there is no function that return all the config at once.

[Tagarno API
documentation](https://t6x6f6w2.rocketcdn.me/wp-content/uploads/2022/12/TAGARNO-Microscope-API-Documentation.pdf)

### Opportunity Picture Properties

---

When a picture is taken, a lot of metadata is record. See
[exif](#exif-information-in-image) for more.

For example :

**Origin properties of image:**

![Alt text](./asssets/image/origin_properties.png)

**Image properties:**

![Alt text](./asssets/image/image_properties.png)

**Camera properties of image:**

![Alt text](./asssets/image/camera_properties.png)

**Advanced photo properties of image:**

![Alt text](./asssets/image/advanced_photo_properties.png)

File properties of image:

![Alt text](./asssets/image/file_properties.png)

Lot's of the recording is also in the Tagarno API (White balance, contrast,
brightness, etc.). If we cross references the data with Tagarno API, we could be
able to only call specific Tagarno function to get a full configuration dataset.

### Tagarno API Get functions

|Function|Configuration|
|---|---|
|getSerial|Request the serial number of the microscope|
|getVersion|Request the application version of the microscope|
|getFieldOfView|Request the current horizontal filed of view in micromillimiter|
|getZoomDirect|Request the current zoom directly from the camera module|
|getFocusDirect|Request the current focus directly from the camera module|
|getAutoFocusMode|Request the current focus mode|
|getAutoExposureMode|Request the current exposure mode|
|getExposureCompensation|Request the current exposure compensation|
|getManualIris|Request the current manual Iris value|
|getManualGain|Request the current manual gain value|
|getManualExposureTime|Request the current manual exposure time|
|getContrast|Request the position of the Contrast slider in Advanced camera settings|
|getSaturation|Request the position of the Saturation slider in Advanced camera settings|
|getSharpness|Request the position of the Sharpness slider in Advanced camera settings|
|getNoiseReduction|Request the position of the Sharpness slider in Advanced camera settings|
|getWhiteBalanceCalibration|Request red and blue calibration gain values|

### Other Tagarno API utilitary functions

|Function|params|return|
|---|---|---|
|captureImage|[bmp, tiff, png, jpg]|return base64 encoded image|
|executeWhiteBalanceCalibration|None|Execute white balance calibration|

### Exif information in Image

---

#### What is exif?

Exif (Exchangeable image file format) a standard that specifies formats for
images, sound, and ancillary tags used by digital cameras (including
smartphones), scanners and other systems handling image and sound files recorded
by digital cameras. [Wikipédia](https://en.wikipedia.org/wiki/Exif)

This format record information that is present in the property of a picture
under details.

### Potential issue

---

We need to carefully exchange image. If a transformation occurs during the
exchange process, exif information can be lost. Therefore, we need to valide if
data follow the picture when it goes through the frontend to the backend.

### List of validation

---

- [ ] Validate that Tagarno image produce exif metadata
- [x] Validate that image coming from frontend also produced exif metadata :x:
- [ ] Find another way to collect metadata from image if they don't recorded
exif
- [ ] Incorporate TIFF tag into the functionnality since Tagarno image are .tiff
extension
- [x] Waiting for Jack to return email on API :x:

### Return on validation

#### Validate that Targano image produce exif metadata

Ongoing

#### Validate that image coming from frontend also produced exif metadata

Nachet frontend send png image to be analyze by the pipelines(list of models).
Therefore, no metadata seems to be taken since PNG file doesn't store exif
information. [png_documentation](https://www.w3.org/TR/png/#11Chunks)

Since we ask the user if we can use is camera, we could explore how the frontend
can access exif data from there, or rewoke the capture picture functionnality to
directly took a picture from the camera that is connected in JPG or TIFF format.
As confirmed by Taran, TIFF format is used at the laboratory and was used to
train our models.

#### Find another way to collect metadata from image if they don't recorded

Ongoing

#### Incorporate TIFF tag into the functionnality since Tagarno image are .tiff

Ongoing

#### Waiting for Jack to return email on API

It was confirm by Anders Ravnskjaer Pedersen (after Jake from Tagarno ask him)
that there is no functionnality in the API that return all the microscope
configuration at once.

In the same email chain, Jill Gagnon confirm that we could not have an extension
on our API trial. The invoice was forwaded by Ricky to Noureddine.

### get exif function

```mermaid
sequenceDiagram
actor Client
participant Frontend
participant Backend
participant Microscope Modul
participant Blob Storage

Client-)Frontend: Upload picture
Frontend-)Backend: HTTP POST req.
Backend-)Backend: inference_request()
note over Backend, Blob Storage: While in inference call <br> see inference doc
Backend-)Microscope Modul: get_picture_details(data)
note over Backend, Microscope Modul: Need to validate what data is pass <br> to the function image or path ?
Microscope Modul-)Backend: return exif_dict
Backend-)Blob Storage: HTTP POST req.
note over Backend, Blob Storage: Storing metadata and image path with the rest of the inference result
```
Empty file added microscope/__init__.py
Empty file.
121 changes: 121 additions & 0 deletions microscope/microscope_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import os
import json
import uuid
import logging
import requests
import base64

Check failure on line 6 in microscope/microscope_info.py

View workflow job for this annotation

GitHub Actions / lint-test / lint-test

Ruff (F401)

microscope/microscope_info.py:6:8: F401 `base64` imported but unused
import io
from PIL import Image, ExifTags

Check failure on line 8 in microscope/microscope_info.py

View workflow job for this annotation

GitHub Actions / lint-test / lint-test

Ruff (F401)

microscope/microscope_info.py:8:24: F401 `PIL.ExifTags` imported but unused
from dotenv import load_dotenv

load_dotenv()

methods_str = os.getenv("METHODS", "[]")
METHODS = json.loads(methods_str)
MICROSCOPE_URL = os.getenv("MICROSCOPE_URL")
params = {"id": int(uuid.uuid4())}
HEADERS = {'Content-Type': 'application/json'}


class MicroscopeQueryError(Exception):
pass


class ExifNonPresentError(Exception):
pass


def post_request(MICROSCOPE_URL, method, params, headers=HEADERS):
'''
This method call Tagarno's API with a specific function and return the result.

:param: MICROSCOPE_URL str
:param: method str
:param: params list
:param: headers dict

:return: json response
'''
url = f"{MICROSCOPE_URL}?jsonrpc=2.0&method={method}&id={params['id']}"

data = json.dumps({
"jsonrpc": "2.0",
"method": method,
"id": params['id'],
})

try:
resp = requests.post(url, data=data, headers=headers)
resp.raise_for_status()
return resp.json()
except requests.RequestException as e:
logging.error(f"Request Error: {e}")
raise MicroscopeQueryError(f"MicroscopeQueryError: {e}") from e

def is_hex(s):
'''
Validate if a value is hexadecimal

:return: bool True or False
'''
try:
int(s, 16)
return True
except ValueError:
return False

def get_microscope_configuration(METHODS):
'''
This method return the actual config of the Tagarno microscope.

:param: METHODES list

:return: config dict
'''
config = {}
for method in METHODS:
try:
resp = post_request(MICROSCOPE_URL, method, params, HEADERS)
result = resp["result"]

# Check if the response is in hexadecimal and convert it
if isinstance(result, str) and is_hex(result):
result = int(result, 16)

config[method] = result

except MicroscopeQueryError as mqe:
config[method] = None
logging.error(f"MicroscopeQueryError: {mqe}")

return config


def get_picture_details(image:bytes) -> dict:
# Source 1 : https://thepythoncode.com/article/extracting-image-metadata-in-python
# Source 2 : https://www.geeksforgeeks.org/how-to-extract-image-metadata-in-python/
'''
Retrieve exif details from picture.
'''

io_byte_image = io.BytesIO(image)
io_byte_image.seek(0)

img = Image.open(io_byte_image)
file = ".".join(["picture_test", "png"])
img.save(file)



# img = Image.open(path)

# full_exif = { ExifTags.TAGS[k]: v for k, v in img._getexif().items() if k in ExifTags.TAGS }
# return full_exif

if __name__ == "__main__":
try:
config = get_microscope_configuration(METHODS)
if config:
print(config)
except requests.RequestException as e:
raise MicroscopeQueryError(f"OpenApiError: {e}") from e
9 changes: 9 additions & 0 deletions tests/test_microscope_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import microscope.microscope_info as m

path=r"/workspaces/nachet-backend/docs/asssets/image/test_02.jpg"

def test_print_exif_info():
data = m.get_picture_details(path)

for key, value in data.items():
print(f"{key:25} : {value}")
Loading