2

Seems possible to do a "post" http request based save.

https://www.tensorflow.org/js/guide/save_load

https://firebase.google.com/docs/functions/http-events

Worried that I may be overcomplicating this, is there a simple way to perhaps create the json files in the client side js (maybe avoiding saving into local storage as well?), then simply upload to storage using put function? https://firebase.google.com/docs/storage/web/upload-files

Not very experienced in these areas and could not find anything specific to this scenario.

Looking into:

seems this is using cloud functions Write image file to Firebase Storage from HTTP function

Update:

Currently looking into using built in Tensorflowjs HTTP request based save (both bin file with weights and model structure json file) that will be handled by a cloud function, not sure if this is the best way to go or if it will work, but best lead I have so far. Also now looking into how to send folder name through http request?

WTJJ
  • 103
  • 10

1 Answers1

3

My client side Tensorflow JS request to save to cloud storage via HTTP request. There is probably a better way to do this but this at least has worked for me.

var api = 'https://us-central1-your-uniquelocation.cloudfunctions.net/uploadModel';
var res = await model.save(tf.io.browserHTTPRequest(api,
        {method: 'POST', headers:{'Authorization':'test','Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryVoB3gVGBQHG0btQR'}}));
console.log(res);

Then for the index.js for my cloud functions

const functions = require('firebase-functions');
var admin = require("firebase-admin");
var serviceAccount = require("./service-account-credentials.json");

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    //databaseURL: "https://yoururl.firebaseio.com",
    storageBucket: "yoururl.appspot.com"
});

var bucket = admin.storage().bucket("yoururl.appspot.com");

const path = require('path');
const os = require('os');
const fs = require('fs');
const Busboy = require('busboy');


exports.uploadModel = functions.https.onRequest((req, res) => {
    res.set('Access-Control-Allow-Origin', '*');
    res.set('Access-Control-Allow-Credentials', 'true');
    if (req.method === 'OPTIONS') {
        // Send response to OPTIONS requests
        console.log("send response 204"); 

        res.set('Access-Control-Allow-Methods', 'GET');
        res.set('Access-Control-Allow-Headers', 'Authorization');
        res.set('Access-Control-Max-Age', '3600');

        res.status(204).send();
     }

    console.log("metho--->"+req.method);
    console.log(req.headers);

    const busboy = new Busboy({headers: req.headers});
    const tmpdir = os.tmpdir();

    // This object will accumulate all the fields, keyed by their name
    const fields = {};

    // This object will accumulate all the uploaded files, keyed by their name.
    const uploads = {};

    // This code will process each non-file field in the form.
    busboy.on('field', (fieldname, val) => {
        // TODO(developer): Process submitted field values here
        console.log(`Processed field ${fieldname}: ${val}.`);
        fields[fieldname] = val;
    });

    const fileWrites = [];
    //res.status(200).send();

    // This code will process each file uploaded.
    busboy.on('file', (fieldname, file, filename) => {
        // Note: os.tmpdir() points to an in-memory file system on GCF
        // Thus, any files in it must fit in the instance's memory.
        console.log(`Processed file ${filename}`);
        const filepath = path.join(tmpdir, filename);
        uploads[fieldname] = filepath;

        const writeStream = fs.createWriteStream(filepath);
        file.pipe(writeStream);

        // File was processed by Busboy; wait for it to be written to disk.
        const promise = new Promise((resolve, reject) => {
            file.on('end', () => {
                writeStream.end();
            });
            writeStream.on('finish', resolve);
            writeStream.on('error', reject);
        });
        fileWrites.push(promise);
    });

    // Triggered once all uploaded files are processed by Busboy.
    // We still need to wait for the disk writes (saves) to complete.
    busboy.on('finish', () => {
        Promise.all(fileWrites).then(() => {
        // TODO(developer): Process saved files here
            for (const name in uploads) {
                const file = uploads[name];
                bucket.upload(file).then(()=>{
                    fs.unlinkSync(file);
                    console.log("saved");
                });
            }
            res.send();
        });
    });
    busboy.end(req.rawBody);
});

Update:

Added functionality to send folder names through the http request as well, once again probably not perfect but works, originally pretty hard to figure out.

New index.js for my cloud functions for those who want custom folder names sent through http request.

const functions = require('firebase-functions');
var admin = require("firebase-admin");
var serviceAccount = require("./service-account-credentials.json");

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    storageBucket: "YOURURL.appspot.com"
});

var bucket = admin.storage().bucket();

const path = require('path');
const os = require('os');
const fs = require('fs');
const Busboy = require('busboy');

exports.uploadModel2s = functions.https.onRequest((req, res) => {
    res.set('Access-Control-Allow-Origin', '*');
    res.set('Access-Control-Allow-Credentials', 'true');
    if (req.method === 'OPTIONS'){
        console.log("send response 204"); 
        res.set('Access-Control-Allow-Methods', 'GET');
        res.set('Access-Control-Allow-Headers', 'Authorization,ModelId');
        res.set('Access-Control-Max-Age', '3600');
        return res.status(204).send();
    }

    console.log("method ---> "+req.method);
    const modelid = req.headers['modelid'];
    console.log("modelid ---> "+modelid);
    console.log(req.headers);

    const busboy = new Busboy({headers: req.headers});
    const tmpdir = os.tmpdir();

    const fields = {};
    const uploads = {};

    busboy.on('field', (fieldname, val) => {
        console.log(`Processed field ${fieldname}: ${val}.`);
        fields[fieldname] = val;
    });

    const fileWrites = [];

    busboy.on('file', (fieldname, file, filename) => {
        console.log(`Processed 2 file ${modelid} ${filename}`);
        const filepath = path.join(tmpdir,filename);
        uploads[fieldname] = filepath;
        console.log('PATH: '+filepath);
        const writeStream = fs.createWriteStream(filepath);
        file.pipe(writeStream);

        const promise = new Promise((resolve, reject) => {
            file.on('end', () => {
                writeStream.end();
            });
            writeStream.on('finish', resolve);
            writeStream.on('error', reject);
        });
        fileWrites.push(promise);
    });

    busboy.on('finish', () => {
        Promise.all(fileWrites).then(() => {
            for (const name in uploads){
                const file = uploads[name];
                const destpath = "/m_"+modelid+"/"+name;
                console.log("SAVE ME HERE: "+destpath);
                bucket.upload(file,{ destination: destpath,public: true }).then(() => {
                    if(name == "model.weights.bin"){
                        fs.unlinkSync(file);
                        console.log("save weights");
                        res.send();
                    }else{
                        console.log("save model");
                    }
                });
            }
        });
    });
    busboy.end(req.rawBody);
});

New client code

var api = 'https://yourproject.cloudfunctions.net/uploadModel2s';
var res = await model.save(tf.io.browserHTTPRequest(api,
                {method: 'POST', headers:{'Authorization':'test','ModelId':m}}));

Security risk

In order to speed development I am using public: true on upload so I can access file easily for download, however this is definitely not secure.

WTJJ
  • 103
  • 10