Training Microsoft Custom Vision model via rest api - node.js

I am working on a simple nodejs console utility that will upload images for the training of a Custom Vision model. I do this mainly because the customvision web app won't let you tag multiple images at once.
tl;dr: How to post images into the CreateImagesFromFiles API endpoint?
I cannot figure out how to pass images that I want to upload. The documentation just defines a string as a type for one of the properties (content I guess). I tried passing path to local file, url to online file and even base64 encoded image as a string. Nothing passed.
They got a testing console (blue button "Open API testing console" at the linked docs page) but once again... it's vague and won't tell you what kind of data it actually expects.
The code here isn't that relevant, but maybe it helps...
const options = {
host: 'southcentralus.api.cognitive.microsoft.com',
path: `/customvision/v2.0/Training/projects/${projectId}/images/files`,
method: 'POST',
headers: {
'Training-Key': trainingKey,
'Content-Type': 'application/json'
}
};
const data = {
images: [
{
name: 'xxx',
contents: 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAEklEQVR42mP8z8AARKiAkQaCAFxlCfyG/gCwAAAAAElFTkSuQmCC',
tagIds: [],
regions: []
}
],
tagIds: []
}
const req = http.request(options, res => {
...
})
req.write(JSON.stringify(data));
req.end();
Response:
BODY: { "statusCode": 404, "message": "Resource not found" }
No more data in response.

I got it working using the "API testing console" feature, so I can help you to identify your issue (but sorry, I'm not expert in node.js so I will guide you with C# code)
Format of content for API
You are right, the documentation is not clear about the content the API is waiting for. I made some search and found a project in a Microsoft's Github repository called Cognitive-CustomVision-Windows, here.
What is saw is that they use a class called ImageFileCreateEntry whose signature is visible here:
public ImageFileCreateEntry(string name = default(string), byte[] contents = default(byte[]), IList<System.Guid> tagIds = default(IList<System.Guid>))
So I guessed it's using a byte[].
You can also see in their sample how they did for this "batch" mode:
// Or uploaded in a single batch
var imageFiles = japaneseCherryImages.Select(img => new ImageFileCreateEntry(Path.GetFileName(img), File.ReadAllBytes(img))).ToList();
trainingApi.CreateImagesFromFiles(project.Id, new ImageFileCreateBatch(imageFiles, new List<Guid>() { japaneseCherryTag.Id }));
Then this byte array is serialized with Newtonsoft.Json: if you look at their documentation (here) it says that byte[] are converted to String (base 64 encoded). That's our target.
Implementation
As you mentioned that you tried with base64 encoded image, I gave it a try to check. I took my StackOverflow profile picture that I downloaded locally. Then using the following, I got the base64 encoded string:
Image img = Image.FromFile(#"\\Mac\Home\Downloads\Picto.jpg");
byte[] arr;
using (MemoryStream ms = new MemoryStream())
{
img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
arr = ms.ToArray();
}
var content = Convert.ToBase64String(arr);
Later on, I called the API with no tags to ensure that the image is posted and visible:
POST https://southcentralus.api.cognitive.microsoft.com/customvision/v2.2/Training/projects/MY_PROJECT_ID/images/files HTTP/1.1
Host: southcentralus.api.cognitive.microsoft.com
Training-Key: MY_OWN_TRAINING_KEY
Content-Type: application/json
{
"images": [
{
"name": "imageSentByApi",
"contents": "/9j/4AAQSkZJRgA...TOO LONG FOR STACK OVERFLOW...",
"tagIds": [],
"regions": []
}
],
"tagIds": []
}
Response received: 200 OK
{
"isBatchSuccessful": true,
"images": [{
"sourceUrl": "imageSentByApi",
"status": "OK",
"image": {
"id": "GENERATED_ID_OF_IMAGE",
"created": "2018-11-05T22:33:31.6513607",
"width": 328,
"height": 328,
"resizedImageUri": "https://irisscuprodstore.blob.core.windows.net/...",
"thumbnailUri": "https://irisscuprodstore.blob.core.windows.net/...",
"originalImageUri": "https://irisscuprodstore.blob.core.windows.net/..."
}
}]
}
And my image is here in Custom Vision portal!
Debugging your code
In order to debug, you should 1st try to submit your content again with tagIds and regions arrays empty like in my test, then provide the content of the API reply

Related

Make a chrome extension download all PDFs with `declarativeNetRequest`

I have to migrate a chrome extension from MV2 to MV3, and that means replacing usages of the blocking chrome.webRequest API with declarativeNetRequest. One usage is this:
function enableDownloadPDFListener() {
chrome.webRequest.onHeadersReceived.addListener(downloadPDFListener);
}
function downloadPDFListener(details) {
const header = details.responseHeaders.find(e => e.name.toLowerCase() === 'content-type');
if (header.value && header.value === 'application/pdf') {
const headerDisposition = details.responseHeaders.find(
e => e.name.toLowerCase() === 'content-disposition'
);
if (headerDisposition) {
headerDisposition.value = headerDisposition.value.replace('inline', 'attachment');
} else {
details.responseHeaders.push({ name: 'Content-Disposition', value: 'attachment' });
}
}
return { responseHeaders: details.responseHeaders };
}
Explanation: This function intercepts requests, checks if their Content-Type header is application/pdf, and if that's the case, sets Content-Disposition: attachment to force downloading the file. We have this functionality to save our employees time when downloading lots of PDF files from various websites.
The problem I'm facing is that this API is deprecated and can't be used in Manifest V3, and I wasn't able to migrate it to the declarativeNetRequest API. I tried the following:
[
{
"id": 1,
"priority": 1,
"action": {
"type": "modifyHeaders",
"responseHeaders": [
{
"header": "content-disposition",
"operation": "set",
"value": "attachment"
}
]
},
"condition": {
// what should I put here?
}
}
]
But I don't know how to filter files with a certain Content-Type header. From what I understand, this is currently not possible. Is there any other way to get this functionality in Chrome's MV3?
I tried { "urlFilter": "*.pdf" } as a condition, which isn't correct, but might be good enough. However, although the badge indicates that the rule was executed, the Content-Disposition header isn't set in the network tab, and the file isn't downloaded. What went wrong here?
At a glance, I don't think there's a condition that would work here. It seems like a reasonable use case and something that may make a good issue at https://bugs.chromium.org/p/chromium though.
In the meantime - could you have an extension which injects a content script that listens to the click event on links? Or alternatively you could perhaps wait for the PDF to open and then close the tab and perform a download.

CDON API RESTful Api GET request

I'm currently working on fetching customer data from cdon, it's an e-commerce platform. They have their API documentation here:
CDON Api Docu
First let me show you my code:
myToken = '<token here>'
myUrl = 'https://admin.marketplace.cdon.com/api/reports/d8578ef8-723d-46cb-bb08-af8c9b5cca4c'
head = {'Authorization': 'token {}'.format(myToken),
'Status':'Online',
'format':'json'}
filters = '?filter={"Status":["Online"],"format": ["json"] }}'
response = requests.get(myUrl + filters, headers=head)
report = response.json()
print(report.products)
This is returning only the parameters. like for example at at this JSON: CDON Github
Status has a value Online this online is a group of itemsthat I only want to get.
What I'm trying to get is a response like this:
{
"Products": [
{
"SKU": "322352",
"Title": "Fabric Cover",
"GTIN": "532523626",
"ManufacturerArticleNumber": "",
"StatusCDON": "Online",
"ExposeStatusCDON": "Buyable",
"InStock": 0,
"InStockCDON": 0,
"CurrentPriceSE": null,
"OrdinaryPriceSE": null,
"CurrentPriceCDONSE": 299.0000,
"OrdinaryPriceCDONSE": null,
"CurrentPriceDK": null,
"OrdinaryPriceDK": null,
"CurrentPriceCDONDK": null,
"OrdinaryPriceCDONDK": null,
"CurrentPriceNO": null,
"OrdinaryPriceNO": null,
"CurrentPriceCDONNO": null,
"OrdinaryPriceCDONNO": null,
"CurrentPriceFI": null,
"OrdinaryPriceFI": null,
"CurrentPriceCDONFI": null,
"OrdinaryPriceCDONFI": null
},
Which means the full list of the items that are Online
How should I put this... among all the API's I tried this one is very confusing, is this even RestFul? If I can achieve the python equivalent of this C# sample code:
public string Post(Guid repordId, string path)
{
var filter = new JavaScriptSerializer().Serialize(new
{
States = new[] { "0" } // Pending state
});
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair("ReportId", repordId.ToString()),
new KeyValuePair("format", "json"),
new KeyValuePair("filter", filter)
});
var httpClient = new HttpClient() { BaseAddress = new Uri("https://admin.marketplace.cdon.com/") };
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("api", ApiKey);
var response = httpClient.PostAsync(path, content).Result;
response.EnsureSuccessStatusCode();
return response.Content.ReadAsStringAsync().Result;
}
I may be able to undestand how this API works, the response that I got was taken manually from their report function in JSON format.
Image
I made many attempts and at that code ( my code ) I stopped, being on this for 4 hours made me give up and ask. Trust that I have searched as many references as I could. It's really confusing.
How do I get the response that I want? Filtering via url? or via header? is this even restful? Help T_T
The documentation states in the first line, emphasis mine:
In order to generate a report you perform a POST call to the reports API with the parameters you wish to use for the report.
Your Python code does not make a POST request, you are trying a GET request. The documentation goes on
[...] to filter on Swedish orders you set the CountryCodes
attribute to “Sweden” and to get returned and cancelled orders you set
the States attribute to 2 and 3. So in the end the filter would look
like this:
{
"CountryCodes": [ "Sweden" ],
"States": ["2", "3"]
}
So you need to prepare a filter object (a dictionary in Python) with the filters you want. Luckily the Python syntax for dictionaries is equivalent (Python is flexible and also allows single-quoted strings):
filter = {
'CountryCodes': [ 'Sweden' ],
'States': [ '0' ]
}
The documentation goes on
You then post the parameters as form data (content-type:
application/x-www-form-urlencoded) so the request body would look like
this:
ReportId=d4ea173d-bfbc-48f5-b121-60f1a5d35a34&format=json&filter={"CountryCodes":["Sweden"],"States":["2","3"]}
application/x-www-form-urlencoded is the default for HTTP post, the requests module knows that and does this for you automatically. All you need to do is to prepare a data dict which will contain the data you want to post.
data = {
'ReportId': 'd4ea173d-bfbc-48f5-b121-60f1a5d35a34',
'format': 'json'
'filter': json.dumps(filter)
}
The filter parameter is supposed to be in JSON format. You must encode that yourself via json.dumps().
import json
head = { ... as above }
filter = { ... as above }
data = { ... as above }
response = requests.post(url, data, header=head)
I'll leave figuring out setting the Authorization header properly as an exercise for you. Partly because it isn't hard, partly because I have no intention of creating an API key with this website just for testing this and partly because it's entirely possible that your current header already works.

Sent a picture in a chatbot using botframework

I have a chatbot using the botframework of Microsoft, with my webapp running the chatbot on Azure. How can I return a picture as an answer to a message. We have clients using Skype, Messenger and KiK
Take a look at the docs section on image and file attachments
replyMessage.Attachments.Add(new Attachment()
{
ContentUrl = "https://upload.wikimedia.org/wikipedia/en/a/a6/Bender_Rodriguez.png",
ContentType = "image/png"
});
Or as JSON:
{
"attachments": [
{
"contentType": "image/png",
"contentUrl": "https://upload.wikimedia.org/wikipedia/en/a/a6/Bender_Rodriguez.png"
}
]
}
You can also send Rich Cards:
replyMessage.Attachments = new List<Attachment>();
replyMessage.Attachments.Add(new Attachment()
{
Title = "Bender",
TitleLink = "https://en.wikipedia.org/wiki/Bender_(Futurama)",
ThumbnailUrl = "http://www.theoldrobots.com/images62/Bender-18.JPG",
Text = "Bender Bending Rodríguez, commonly known as Bender, is a main character in the animated television series Futurama.",
FallbackText = "Bender: http://www.theoldrobots.com/images62/Bender-18.JPG"
});

Cache all images with onHeadersReceived

I'm trying to modify the response headers of the images to save bandwith and improve the response time.These are my files:
manifest.json
{
"name": "Cache all images",
"version": "1.0",
"description": "",
"background": {"scripts": ["cacheImgs.js"]},
"permissions": [ "<all_urls>", "webRequest", "webRequestBlocking" ],
"icons": {"48": "48.png"},
"manifest_version": 2
}
cacheImgs.js
var expDate = new Date(Date.now()+1000*3600*24*365).toUTCString();
var newHeaders =
[{name : "Access-Control-Allow-Origin", value : "*"},
{name : "Cache-Control", value : "public, max-age=31536000"},
{name : "Expires", value : expDate},
{name : "Pragma", value : "cache"}];
function handler(details) {
var headers = details.responseHeaders;
for(var i in headers){
if(headers[i].name.toLowerCase()=='content-type' && headers[i].value.toLowerCase().match(/^image\//)){
for(var i in newHeaders) {
var didSet = false;
for(var j in headers) {
if(headers[j].name.toLowerCase() == newHeaders[i].name.toLowerCase() ) {
headers[j].value = newHeaders[i].value;
did_set = true; break;
}
}
if(!didSet) { headers.push( newHeaders[i] ); }
}
break;
}
}
console.log(headers);
return {responseHeaders: headers}
};
var requestFilter = {urls:['<all_urls>'], types: ['image'] };
var extraInfoSpec = ['blocking', 'responseHeaders'];
chrome.webRequest.onHeadersReceived.addListener(handler, requestFilter, extraInfoSpec);
the console.log fires many times and i can see the new headers. The problem is that when I open the chrome developer tools of the page, in the network tab, i see the same original headers of the images. Also note the blocking value in the extraInfoSpec, so that's supposed to be synchronous. Does someone happen the same?
UPDATE
Now I see the modified response headers in the network panel.
But now I only see from images whose initiator is the webpage itself. The images whose initiator are jquery.min.js doesn't change the response headers
There are two relevant issues here.
First, the headers displayed in the developer tools are those that are received from the server. Modifications by extensions do not show up (http://crbug.com/258064).
Second (this is actually more important!), modifying the cache headers (such as Cache-control) has no influence on the caching behavior of Chromium, because the caching directives have already been processed when the webRequest API is notified of the headers.
See http://crbug.com/355232 - "Setting caching headers in webRequest.onHeadersReceived does not influence caching"
After doing some research, it turns out my previous answer was wrong. This is actually a Chrome bug - Chrome's DevTools Network panel will only show the actual headers received from the server. However, the headers you've injected will still have the desired effect.
Another extension developer identified the issue here and provided a link to the Chrome defect report

Getting url for an attachment

I'm using CouchApp to build an easy web application that allows to upload and manage pictures. The actual image file is stored as attachment to the doc like show below.
{
"_id":"09fe82d75a26f9aa5e722d6b220180d2",
"_rev":"2-5797b822c83b9d41545139caa592f611",
"data":"some additional fields with info about the image",
"_attachments":
{
"foo.jpg":
{
"stub":true,
"content_type":"image/jpeg",
"length":23721
}
}
}
But for integrating the image in html i need the url to the attachment. How do i get this url?
I'm using evently and mustache for generating the web pages. Below is the data.js for reading the data:
function(data) {
return {
items : data.rows.map(function(r) {
return {
id : r.value._id,
rev : r.value._rev,
title : r.value.description,
url : "how am i supposed to do this?"
};
})
};
};
The URL to the attachment would be http://domain/database/09fe82d75a26f9aa5e722d6b220180d2/foo.jpg
If your filenames are dynamic, you would have to iterate the _attachments object and fetch the keys on your way - that's where your filename would be.
for(var filename in r.value._attachments){break;}
// ...
url : '<database>/' + r.value._id +'/'+filename;

Resources