A draft.
This is a step-by-step tutorial for requesting Fishial Recognition on an example fish picture.
In this tutorial, we’ll show step-by-step how to perform fish recognition on an above picture of a northern pike, which can be downloaded from here: https://raw.githubusercontent.com/fishial/devapi/main/fishpic.jpg
curl "https://raw.githubusercontent.com/fishial/devapi/main/fishpic.jpg" -o fishpic.jpg
In this tutorial, it is assumed that user has a Developers' API subscription and has generated API credentials (API Key Id and API Secret Key).
At first, following information about an image must be gathered: its file name, MIME type, byte size, and MD5 checksum. It is required to upload an image to the cloud.
The picture has been saved as fishpic.jpg
, thus its file name is
fishpic.jpg
, obviously. (Note: this must be a base name, that is without
a directory path)
A list of common MIME types can be found on Mozilla’s MDN pages:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types.
The picture is a JPEG image, hence its MIME type is image/jpeg
.
There are several tools to identify image’s MIME type automatically. Perhaps
the most convenient one is a file
program that is available on many UNIX-like
systems:
$ file --mime-type -b fishpic.jpg image/jpeg
A byte size is a number of bytes that given file is composed of.
One way to get this number is wc -c
command:
$ wc -c fishpic.jpg 2204455 fishpic.jpg
Finally, an MD5 checksum needs to be calculated for an image. A checksum is required to be Base64-encoded for transport purposes.
There are many ways to get a checksum, for example by using OpenSSL toolkit:
$ openssl dgst -md5 -binary < fishpic.jpg | openssl enc -base64 EA5w4bPQDfzBgEbes8ZmuQ==
In this tutorial, following API credentials will be used.
API Key ID |
c0fae174f24c0950352c2bbd |
API Secret Key |
5edac99f92bf7acb66425c47fb153c5f |
To prevent from abuse, the API key shown in this tutorial has been already deleted. Don’t use it as it won’t work, please use your own instead.
Warning
|
Never reveal API Secret Key nor pass it to untrusted machines. |
Now it’s time to get an access token. This is done by sending API credentials
to https://api-users.fishial.ai/v1/auth/token
, as illustrated in a cURL
snipped below:
curl --request POST \ --url https://api-users.fishial.ai/v1/auth/token \ --header 'Content-Type: application/json' \ --data '{ "client_id": "c0fae174f24c0950352c2bbd", "client_secret": "5edac99f92bf7acb66425c47fb153c5f" }'
The response includes a bearer token that should be used in Authorization
HTTP
header in subsequent API calls.
{ "token_type": "Bearer", "access_token": "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTY2MTkzNzQsImtpZCI6ImMwZmFlMTc0ZjI0YzA5NTAzNTJjMmJiZCJ9.SwJITJXPcGXu9E6UYPNXq0kTfPRZ_O-qe3FGV7ZTxZM" }
Note
|
The Access Token is valid for 10 minutes and can be passed to client machines without compromising API Secret Key. |
Before we actually upload a picture, we need to get a signed URL that allows us to do so. The request body includes image metadata that were obtained earlier. The signed URL won’t allow a file that doesn’t match that metadata.
curl --request POST \ --url https://api.fishial.ai/v1/recognition/upload \ --header 'Accept: application/json' \ --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTY2MTkzNzQsImtpZCI6ImMwZmFlMTc0ZjI0YzA5NTAzNTJjMmJiZCJ9.SwJITJXPcGXu9E6UYPNXq0kTfPRZ_O-qe3FGV7ZTxZM' \ --header 'Content-Type: application/json' \ --data '{ "blob": { "filename": "fishpic.jpg", "content_type": "image/jpeg", "byte_size": 2204455, "checksum": "EA5w4bPQDfzBgEbes8ZmuQ==" } }'
The response is quite lengthy and looks like this:
{ "id": 1399956, "key": "hpleg1o4wez3e7mvy1sivhep9dw6", "filename": "fishpic.jpg", "content-type": "image/jpeg", "metadata": {}, "byte-size": 2204455, "checksum": "EA5w4bPQDfzBgEbes8ZmuQ==", "created-at": "2022-06-30T19:53:07.695Z", "service-name": "google", "signed-id": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBNVJjRlE9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--269b0ee106c8739e2248645965ad0fc868b20c7c", "attachable-sgid": "BAh7CEkiCGdpZAY6BkVUSSJPZ2lkOi8vZmlzaGlhbC1wb3J0YWwtYmFja2VuZC1maXNoZXMvQWN0aXZlU3RvcmFnZTo6QmxvYi8xMzk5OTU2P2V4cGlyZXNfaW4GOwBUSSIMcHVycG9zZQY7AFRJIg9hdHRhY2hhYmxlBjsAVEkiD2V4cGlyZXNfYXQGOwBUMA==--f33ed3bd1d50febd02cd1986655805c71b3699ea", "direct-upload": { "url": "https://storage.googleapis.com/backend-fishes-storage-prod/hpleg1o4wez3e7mvy1sivhep9dw6?GoogleAccessId=services-storage-client%40ecstatic-baton-230905.iam.gserviceaccount.com&Expires=1656619087&Signature=aMeN%2FVW4I6LDgLRUEW0jzJqeA89gRpjn0TBcGMEnal3RU1iGqe0uOraHainV5qUw6tRPEBRBJ6rMeu9x0AmX6OC3Q8cCCmBUMs4k1jCQOnnCNgkttpU7ov%2FeZ9WpPr47rTQSd5np7jCG3EWVEBhNeP25%2BTx5JlKQQ8UErP%2Bc46Lr%2Bj28wgLw%2BeeVjO4sVjEnLx3djoZD0Htei5XR0YQKVr%2FGDbS4iBOmjPsD5g4txKM6071zft%2BFK6U7I%2FfWjef2w4Nx%2BVvdATkNRpVEzbkAv1lBWMhfrOLy5koJfGepFk0BGQbTXNokhFCQYxzQuJBeC1xubKHzZSQLCVnTXt4EXA%3D%3D", "headers": { "Content-MD5": "EA5w4bPQDfzBgEbes8ZmuQ==", "Content-Disposition": "inline; filename=\"fishpic.jpg\"; filename*=UTF-8''fishpic.jpg" } } }
However, only a few entries are important for us.
signed-id
-
Will be used at the last step.
direct-upload.url
-
A URL that picture should be submitted to.
direct-upload.headers
-
A set of headers that should be set when uploading an image.
Having a signed URL (i.e. direct-upload.url
from the previous step), we may
actually send an image to that URL. Please note that the request method is
PUT
, not usual POST
:
curl --request PUT \ --url 'https://storage.googleapis.com/backend-fishes-storage-prod/hpleg1o4wez3e7mvy1sivhep9dw6?GoogleAccessId=services-storage-client%40ecstatic-baton-230905.iam.gserviceaccount.com&Expires=1656619087&Signature=aMeN%2FVW4I6LDgLRUEW0jzJqeA89gRpjn0TBcGMEnal3RU1iGqe0uOraHainV5qUw6tRPEBRBJ6rMeu9x0AmX6OC3Q8cCCmBUMs4k1jCQOnnCNgkttpU7ov%2FeZ9WpPr47rTQSd5np7jCG3EWVEBhNeP25%2BTx5JlKQQ8UErP%2Bc46Lr%2Bj28wgLw%2BeeVjO4sVjEnLx3djoZD0Htei5XR0YQKVr%2FGDbS4iBOmjPsD5g4txKM6071zft%2BFK6U7I%2FfWjef2w4Nx%2BVvdATkNRpVEzbkAv1lBWMhfrOLy5koJfGepFk0BGQbTXNokhFCQYxzQuJBeC1xubKHzZSQLCVnTXt4EXA%3D%3D' \ --header 'Content-Disposition: inline; filename=\"fishpic.jpg\"; filename*=UTF-8'\'''\''fishpic.jpg' \ --header 'Content-Md5: EA5w4bPQDfzBgEbes8ZmuQ==' \ --header 'Content-Type:' \ --data-binary @fishpic.jpg
There is an empty response body on successful request, and an error message otherwise.
You need to be very careful when setting headers for the request. Actually,
you should only set headers that were returned along with an upload URL
(i.e. in direct-upload.headers
in the previous step), here
Content-Disposition
and Content-Md5
. Extra headers often lead to signature
rejection, though some (e.g. Accept
) are safe to use.
Some tools tend to add some standard headers on their own. For example,
cURL sets Content-Type
to application/x-www-form-urlencoded
by default,
and this must be overridden. Note that setting this header to image/jpeg
wouldn’t work either, despite this is a JPEG file indeed, because it wasn’t
included in direct-upload.headers
.
A Content-Disposition
header value in the above example may look odd, but it’s
only due to Bash escaping. In fact, there are only two single quotes between
UTF-8
and fishpic.jpg
, as returned from the previous request.
Then actual fish recognition may be performed. There is only one query
parameter q
that is set to signed-id
returned along with an upload URL.
curl --request GET \ --url 'https://api.fishial.ai/v1/recognition/image?q=eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBNVJjRlE9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ%3D%3D--269b0ee106c8739e2248645965ad0fc868b20c7c' \ --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTY2MTkzNzQsImtpZCI6ImMwZmFlMTc0ZjI0YzA5NTAzNTJjMmJiZCJ9.SwJITJXPcGXu9E6UYPNXq0kTfPRZ_O-qe3FGV7ZTxZM'
The response is quite lengthy, and it was redacted for clarity:
{ "results": [ { "shape": { <redacted> }, "species": [ { "name": "Esox lucius", "accuracy": 1.0, "fishangler-id": "2a8d971e-de97-4295-8701-b4ea1a8f4da1", "fishangler-data": <redacted> }, { "name": "Esox masquinongy", "accuracy": 0, "fishangler-id": "b9db34f3-4edb-4d6e-8fc6-f6588ca20299", "fishangler-data": <redacted> }, { "name": "Salvelinus fontinalis", "accuracy": 0, "fishangler-id": "95672e98-93fa-4c16-b119-e384e6527424", "fishangler-data": <redacted> }, { "name": "Sander vitreus", "accuracy": 0, "fishangler-id": "b1d261d9-eca8-4fdd-ae09-ca5257babf1b", "fishangler-data": <redacted> } ] } ] }
Its structure reads as follows:
-
The
results
array contains fish shapes that were recognized on given image. -
The
results[n].shape
is a sequence of points that given fish shape consists of. -
The
results[n].species
is an array of species that were matched, withaccuracy
subfield indicating match probability (0 is the lowest, 1 is the highest).
In this case, results
array contains only one item, and one of the matched
species has accuracy 1.0. That means that according to Fishial AI, there’s
only one fish on a picture, and it’s a pike (Esox lucius), which is correct.
Finally, a user may wish to send a feedback agreeing with a recognition result:
curl --request POST \ --url https://api.fishial.ai/v1/ai-feedbacks/44062/entries \ --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTY2MTkzNzQsImtpZCI6ImMwZmFlMTc0ZjI0YzA5NTAzNTJjMmJiZCJ9.SwJITJXPcGXu9E6UYPNXq0kTfPRZ_O-qe3FGV7ZTxZM' \ --header 'Content-Type: application/json' \ --data '{ "data":{ "type": "ai-feedback-entries", "attributes": { "polygon-id": "0", "feedback-type": "Agree" } } }'
Or disagreeing with it:
curl --request POST \ --url https://api.fishial.ai/v1/ai-feedbacks/44062/entries \ --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTY2MTkzNzQsImtpZCI6ImMwZmFlMTc0ZjI0YzA5NTAzNTJjMmJiZCJ9.SwJITJXPcGXu9E6UYPNXq0kTfPRZ_O-qe3FGV7ZTxZM' \ --header 'Content-Type: application/json' \ --data '{ "data":{ "type": "ai-feedback-entries", "attributes": { "polygon-id": "0", "feedback-type": "Disagree", "suggested-name": "Sander vitreus" } } }'
Where:
-
polygon-id
is an item index in the recognition’sresults
array (starting with 0) that this user feedback refers to. It also may benull
when a feedback doesn’t refer to any specific fish shape in the results list but to an image as whole, e.g. when there’s some undetected fish on a picture or a picture doesn’t actually show any fish. -
feedback-type
is one ofAgree
,Disagree
, orUnknown
, and means if a user agreed with recognition result or not. -
suggested-name
is a species name suggested by a user (makes sense only for aDisagree
feedback type).