How to upload image and pass in to tensorflowjs model to get prediction using reactjs? - reactjs

EDIT
Using the graph model format and the updated code example, I've managed to get it to return a prediction. Issue is now it always returns 1, no matter which image I feed it, so wondering if I am not passing in the right image data?
Second EDIT: Changed the way I was passing in the img object, but still getting 1 for every image I feed it.
I have only just started looking into tensorflowjs and am using a prebuilt keras model I have been given access to. This model is a binary classifier. The model has been saved as an .h5 file and I have been asked to run it in the browser using tensorflowjs and react. Essentially I want to select an image from my local storage or an sd card and feed it to the model to get a yes or no classification.
I’ve followed the tensorflowjs docs in converting the keras model to a TF.js Layers format, but then can’t load the model. I’m getting an error about an unknown layer: RandomFlip. So I then tried converting the model to a graph model as I couldn’t find a solution to the error and thought I’d give it a try. This loaded the model but then there were more issues when feeding it the image. The shape of dict['image_tensor'] provided in model.execute(dict) must be [-1,380,380,1], but was [380, 380] . Then I searched for that and got it to resize to [-1,380,380,1] , but then it was complaining about size not being the expected, so I thought maybe I've messed up in some of the previous steps.
To convert to a graph model I used the following command: tensorflowjs_converter --input_format keras --output_format tfjs_layers_model /Users/myUser/Documents/save_at_45.h5 /Users/myUser/Documents/convert-keras-model and in my code loading it with the loadGraphModel method. Following this path has at least allowed me to load the model.
I also tried converting it to a Layers format with: tensorflowjs_converter --input_format keras --output_format tfjs_layers_model /Users/myUser/Documents/save_at_45.h5 /myUser/mariomendes/Documents/convert-keras-model and in my code loading it with the loadLayersModel. This returns the error Unknown layer: RandomFlip. I've tried searching for a solution to this, but haven't been able to find one.
Does knowing it is a .h5 file mean I should know if it needs to be converted to a tf Graph format or Layers format or is there something else that determines which format it should be converted to?
I've stored the converted model in both formats and it's weights in S3 and am getting it from there.
For my react code I have done the following:
import React, { useState, useEffect } from "react";
import "./index.css";
import * as tf from "#tensorflow/tfjs";
function ImgImporter() {
const [file, setFile] = useState(null);
const [model, setModel] = useState(null);
const [processing, setProcessing] = useState(false);
const [prediction, setPrediction] = useState(null);
const [imageLoaded, setImageLoaded] = useState(false);
function readImage(file) {
return new Promise((rs, rj) => {
const fileReader = new FileReader();
fileReader.onload = () => rs(fileReader.result);
fileReader.onerror = () => rj(fileReader.error);
fileReader.readAsDataURL(file);
});
}
async function handleImgUpload(event) {
const {
target: { files },
} = event;
const _file = files[0];
const fileData = await readImage(_file);
setFile(fileData);
setProcessing(true);
}
useEffect(() => {
async function loadModel() {
if (!model) {
const _model = await tf.loadGraphModel("/model.json");
setModel(_model);
}
}
loadModel();
});
useEffect(() => {
async function predict() {
if (imageLoaded && file) {
const imageElement = document.createElement("img");
imageElement.src = file;
imageElement.onload = async () => {
const tensor = tf.browser
.fromPixels(imageElement, 1)
.resizeNearestNeighbor([380, 380])
.expandDims()
.toFloat();
const prediction = await model.predict(tensor).data();
setPrediction(parseInt(prediction, 10));
setProcessing(false);
setImageLoaded(false);
};
}
}
predict();
}, [imageLoaded, model, file]);
return (
<div className="File-input-container">
<form className="Form">
<label htmlFor="upload-image">Upload image</label>
<input
id="image-selector"
type="file"
name="upload-image"
accept="image/*"
className="File-selector"
onChange={handleImgUpload}
disabled={!model || processing}
/>
</form>
<div className="Img-display-container">
<img
onLoad={() => {
setImageLoaded(true);
}}
alt=""
src={file}
/>
</div>
<div className="Img-processing-container">
{processing ? (
<p>Loading ...</p>
) : prediction !== null ? (
<div>
<p>{prediction === 1 ? "Yes" : "No"}</p>
</div>
) : null}
</div>
</div>
);
}
export default ImgImporter;
When I upload an image this is returning the following result in the console as the value of prediction:
dataId: {id: 195}
dtype: "float32"
id: 94
isDisposedInternal: false
kept: false
rankType: "2"
scopeId: 6
shape: (2) [1, 1]
size: 1
strides: [1]
Would be great if someone could shed some light on this or help me finding the right direction.

If you want to get the value, you can use prediction.dataSync() or its promise counterpart await prediction.data()

Regarding your second edit. You're converting the image to a float, but does the model expect a normalized float? You might need to append .div(255) or whatever normalization is needed. Please post the specs for your model.
Also, as stated by edkeveked, you can used dataSync() to get your data, but it's worth noting you could have also used arraySync too, which would maintain the returned tensor depth.
Also, I noticed you didn't do any cleanup. So your tensors will build up in GPU memory. Don't forget to dispose.
I hope these things help.

Related

React - save components visualization to JSON and read it back

Imagine I have a hierarchy of react components, e.g. meals of the day
|------- breakfast --------|
| Meal1_image.png banana |
| Meal2_image.png porridge|
|------- lunch-------------|
| Meal3_image.png toast |
| Meal4_image.png apple |
I can add meals to a meal group (e.g. to the lunch group), I can add more lunch groups (e.g. midday snack) and so on.. it's a variable list of components and I give the user the ability to add meals and meal groups with '+' buttons, or to delete them and so on.
How would I go to save these in a text json and to read them back?
I read about "serializing react components" but maybe I don't need all of that stuff, I would just need to save a json like
{
"breakfast": {
{"food": "banana", "image": "Meal1_image.png"},
{"food": "porridge", "image": "Meal2_image.png"},
},
"lunch" : ... and so on ...
}
is there any simple way to achieve this or should I just go with components serialization with things like https://github.com/zasource-dev/React-Serialize ?
Question is basically too wide, but anyway, just split your task by subtasks.
Design the data models you are going to work with. Create a prototype of it, try to render it as is. Regarding your prototype - that is not valid js object, so i changed it a bit in my example. Note - for .map methods you need to use unique keys.
Figure out what will be the best place to keep your data (in state). You can store it in some component, you can store it in context and add all the needed methods to it and pass them down to your components. I did everything in a component for simplicity of example.
Add download/upload/parsing functions and wire everything up.
If I got your question right - you want each Client to be able to download and upload the Data to a local JSON file on Client, so no server interaction.
const { useState } = React;
function download(content, mimeType, filename) {
const a = document.createElement("a");
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
a.setAttribute("href", url);
a.setAttribute("download", filename);
a.click();
}
function upload(event, callback) {
const input = event.target;
const reader = new FileReader();
reader.onload = function () {
const text = reader.result;
callback(text);
};
reader.readAsText(input.files[0]);
}
const DEFAULT_DATA = {
breakfast: [
{ food: "banana", image: "Meal1_image.png" },
{ food: "porridge", image: "Meal2_image.png" }
],
lunch: [{ food: "banana", image: "Meal1_image.png" }]
};
function App() {
const [data, setData] = useState(DEFAULT_DATA);
const onDownloadClick = (e) => {
download(JSON.stringify(data), "application/json", "file1.json");
};
const onUpload = (e) => {
upload(e, (text) => setData(JSON.parse(text)));
};
return (
<div className="App">
{Object.entries(data).map(([section, items]) => (
<div key={section}>
<p>{section}</p>
<ul>
{items.map((item) => (
<li key={item.food}>
<p>{item.food}</p>
</li>
))}
</ul>
</div>
))}
<button onClick={onDownloadClick} type="button">
Download JSON
</button>
<input type="file" accept="application/json" onChange={onUpload} />
</div>
);
}
// v18.x+
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>
How to test - Download JSON file, open it and change something in it, like "banana" to "banana111", save, upload the file back and observe the updated values.
Not sure what is the plan about Images, but in worst case - you can store images in JSON as base64 strings.
If you mean physical saving in a file, then you need to have node js to access the file system of your phone or PC, your server:
// file system module to perform file operations
const fs = require('fs');
// json data
var jsonData = '{"persons":[{"name":"John","city":"New York"},{"name":"Phil","city":"Ohio"}]}';
// parse json
var jsonObj = JSON.parse(jsonData);
console.log(jsonObj);
// stringify JSON Object
var jsonContent = JSON.stringify(jsonObj);
console.log(jsonContent);
fs.writeFile("output.json", jsonContent, 'utf8', function (err) {
if (err) {
console.log("An error occured while writing JSON Object to File.");
return console.log(err);
}
console.log("JSON file has been saved.");
});
If we are talking about transformation for axios request, then you need to use the following javascript methods JSON.stringify() for json transformation and JSON.parse() for reading to transformation in javascript.
If you just want to transform the object in the hook state, this is also fine.

AJax issue while loading google chart in react

I am trying to use Google chart in React. I read the data from the database and use hook(useState) to save the data. But I can't load the google chart when I fetch the data. I guess it is Ajax problem. I have tried to copy the data that I read from console.log and paste it, it works, so it is not the data format issue.
//inistialise
const [v1, setV1] = useState(['x','sensor'],[0,0])
//define data value
const data = v1;
//define option for Chart
const options = {
title: "Box Office Earnings in First Two Weeks of Opening",
subtitle: "in millions of dollars (USD)",
interpolateNulls: true
};
//fetch data from database and change the data format
async function getAllSensorProfile(sensorIdList, sensorSubId, starttime, endtime) {
sensorIdList = JSON.stringify(sensorIdList)
await deviceService.GetTimeSeriesListBySensorIdListTimeRange(sensorIdList, sensorSubId, starttime, endtime).then(
(result) => {
var time = []
var _v1 = [['x', 'dogs']]
result.map((str) => (
str[1] = parseFloat(str[1])
))
_v1 = _v1.concat(result)
setV1(JSON.stringify(_v1))
console.log(v1)
},
error => {
console.log(error)
}
)
}
<Chart
chartType="LineChart"
data={this.state.data}
options={options}
width={"70%"}
height={"400px"}
/>
I have fixed it! OMG I had this issue for a few days but could not fix it. Now I find the problem!
So the problem is, setV1(JSON.stringfy(_v1)) is converting v1 to be a string, however, in <Chart>, data={data} only accept object, not a string.
So, I simply just change it to setV1(_v1) then it works!

Tensorflow.js predict returning NaNs

I converted a keras model into a tensorflowjs model using the simple tensorflowjs_converter --input_format keras ./model/L_keypoint_classifier_final.h5 L_layer_model. I managed to get this model working on a .ts (TypeScript) file.
Now I am focused on deploying this model using React and Typescript (in a .tsx file). My app component is loading the models as such:
const [models, setModels] = useState<Models>({
L_Model: undefined,
R_Model: undefined,});
useEffect(() => {
loadModel().then((models) => {
setModels(models);
setIsLoading(false);
}); }
The loadModel() function is exported from another file and it is:
export async function loadModel() {
let result: Models = { R_Model: undefined, L_Model: undefined };
result.R_Model = await tf.loadLayersModel("/right/model.json");
result.L_Model = await tf.loadLayersModel("/left/model.json");
return result;
}
That directory of the models is in the public folder of my project. After loading the models in the app component, I pass them to a child component using props.
<Camera models={models}></Camera>
They are received in the camera component as:
const Camera: FunctionComponent<CameraProps> = (props) => {
const { R_Model, L_Model } = props.models;
In the camera component I pass in a tf.Tensor2D. This tensor does in fact contain values that I checked. But when I pass them to the model.predict() function, it just returns a tensor full of NaNs.
This is my code for preprocessing the input and passing it to the model:
//Preprocess Landmarks
//#ts-ignore
let landmark_list = calc_landmark_list(landmarks);
landmark_list = pre_process_landmarks(landmark_list);
//#ts-ignore
landmark_list = tf.tensor2d([landmark_list]);
console.log(landmarks_list.dataSync());
let prediction;
if(isRightHand){
prediction = R_Model?.predict(landmark_list);
}else{
prediction = L_Model?.predict(landmark_list);
}
const scores = prediction.arraySync()[0];
After that, I try to find the maxScore of the predictions, but since arraySync() returns a NaN array, it does not work. My team and me have try searchig for different options. Some include wrapping the predict function inside an aasync function, but that doesn't seem to work either (or maybe we have done it incorrectly, although we have followed the examples thoroughly).
The console.log of the landmark_list.dataSync() prints out:
Float32Array(42) [0, 0, -0.2683601677417755, -0.1023331806063652, -0.4781370162963867, -0.397993803024292, -0.5191399455070496, -0.6676312685012817, -0.46050554513931274, -0.8477477431297302, -0.30691489577293396, -0.9023468494415283, -0.49582260847091675, -1, -0.5734853148460388, -0.7551659941673279, -0.5509241223335266, -0.5708747506141663, -0.15572300553321838, -0.9109046459197998, -0.38624173402786255, -0.9391834735870361, -0.4641483426094055, -0.6930190920829773, -0.4609870910644531, -0.49743616580963135, -0.00984301045536995, -0.8530527353286743, -0.25299814343452454, -0.7750100493431091, -0.32405075430870056, -0.5182365775108337, -0.32825687527656555, -0.3154793083667755, 0.11740472167730331, -0.7356364130973816, -0.12479904294013977, -0.6477926969528198, -0.21985816955566406, -0.43255504965782166, -0.24492989480495453, -0.25398018956184387, buffer: ArrayBuffer(168), byteLength: 168, byteOffset: 0, length: 42, Symbol(Symbol.toStringTag): 'Float32Array']

Wordpress Gutenberg PluginDocumentSettingPanel not working with controls?

I want to add a custom meta field to the gutenberg document panel and used this doc. For the custom meta field I used this tutorial.
The problem I have occurs when trying to put them together.
Here is my code so far:
const { __ } = wp.i18n;
const { registerBlockType } = wp.blocks;
const { InspectorControls } = wp.editor;
const { registerPlugin } = wp.plugins
const { PluginDocumentSettingPanel } = wp.editPost
const { PanelBody, PanelRow, TextControl } = wp.components
const PluginDocumentSettingPanelDemo = () => (
<PluginDocumentSettingPanel
name="custom-panel"
title="Custom Panel"
className="custom-panel"
>
<TextControl
value={wp.data.select('core/editor').getEditedPostAttribute('meta')['_myprefix_text_metafield']}
label={ "Text Meta" }
onChange={(value) => wp.data.dispatch('core/editor').editPost({meta: {_myprefix_text_metafield: value}})}
/>
</PluginDocumentSettingPanel>
)
registerPlugin('plugin-document-setting-panel-demo', {
render: PluginDocumentSettingPanelDemo
})
Edit: Thanks to Ivan I solved this side issue :)
My Sidebar looks okay at first:
But when I try to change the inputs value it isn't updated (but the storage in wp.data is). I can't delete it, too. It stays at it's initial value. When I remove the part where I set the initial value it works like it should be but since I need the initial value this isn't an option for me ;)
Here an example log from the console when I add an "x" to the end of the input (as mentioned above the text in the input itself doesn't change)
Does anyone know how to make the input field working properly?
First of all, make sure you have https://wordpress.org/plugins/gutenberg/ plugin installed, because PluginDocumentSettingPanel is not fully implemented in core WP yet. It should be for 5.3 version, as per these tweets.
Second, you don't need the interval function for the wp.plugins. The reason it is undefined at first is that WordPress doesn't know that you need the wp-plugins loaded first. From the WordPress documentation
If you wanted to use the PlainText component from the editor module, first you would specify wp-editor as a dependency when you enqueue your script
The same applies for all other modules (read scripts, like 'wp-plugins').
You have to add the 'wp-plugins' script as a dependency, when registering your js plugin script:
<?php
/*
Plugin Name: Sidebar plugin
*/
function sidebar_plugin_register() {
wp_register_script(
'plugin-sidebar-js',
plugins_url( 'plugin-sidebar.js', __FILE__ ),
array( 'wp-plugins', 'wp-edit-post', 'wp-element' ) // <== the dependencies array is important!
);
}
add_action( 'init', 'sidebar_plugin_register' );
function sidebar_plugin_script_enqueue() {
wp_enqueue_script( 'plugin-sidebar-js' );
}
add_action( 'enqueue_block_editor_assets', 'sidebar_plugin_script_enqueue' );
The PHP above is taken from the official WP documentation.
I would also suggest reading thoroughly this awesome tutorial from Css Tricks. It goes in depth about setting up an ESNext environment with only the #wordpress/scripts package. It goes over the dependencies, adding meta fields and much more :) I hope this helps!
--------------- Initial answer ends here ---------------
Edit: After testing the code from the author, I found out a couple of issues. First of all, there was a missing closing tag for the TextControl. Second, I added Higher order components from the wp-data package, which I then used to "enhance" the TextControl, so that it doesn't manipulate or read data directly, but abstract that logic into it's higher order components. The code looks like so:
const { __ } = wp.i18n;
const { registerPlugin } = wp.plugins;
const { PluginDocumentSettingPanel } = wp.editPost;
const { TextControl } = wp.components;
const { withSelect, withDispatch, dispatch, select } = wp.data;
// All the necessary code is pulled from the wp global variable,
// so you don't have to install anything
// import { withSelect, withDispatch, dispatch, select } from "#wordpress/data";
// !!! You should install all the packages locally,
// so your editor could access the files so you could
// look up the functions and classes directly.
// It will not add to the final bundle if you
// run it through wp-scripts. If not, you can
// still use the wp global variable, like you have done so far.
let TextController = props => (
<TextControl
value={props.text_metafield}
label={__("Text Meta", "textdomain")}
onChange={(value) => props.onMetaFieldChange(value)}
/>
);
TextController = withSelect(
(select) => {
return {
text_metafield: select('core/editor').getEditedPostAttribute('meta')['_myprefix_text_metafield']
}
}
)(TextController);
TextController = withDispatch(
(dispatch) => {
return {
onMetaFieldChange: (value) => {
dispatch('core/editor').editPost({ meta: { _myprefix_text_metafield: value } })
}
}
}
)(TextController);
const PluginDocumentSettingPanelDemo = () => {
// Check if a value has been set
// This is for editing a post, because you don't want to override it everytime
if (!select('core/editor').getEditedPostAttribute('meta')['_myprefix_text_metafield']) {
// Set initial value
dispatch('core/editor').editPost({ meta: { _myprefix_text_metafield: 'Your custom value' } });
}
return (
<PluginDocumentSettingPanel
name="custom-panel"
title="Custom Panel"
className="custom-panel"
>
<TextController />
</PluginDocumentSettingPanel>
)
};
registerPlugin('plugin-document-setting-panel-demo', {
render: PluginDocumentSettingPanelDemo
})
Since the meta field is registered with an underscore as a first symbol in the name, WordPress will not allow you to save it, because it treats it as a private value, so you need to add extra code, when registering the field:
function myprefix_register_meta()
{
register_post_meta('post', '_myprefix_text_metafield', array(
'show_in_rest' => true,
'type' => 'string',
'single' => true,
'sanitize_callback' => 'sanitize_text_field',
'auth_callback' => function () {
return current_user_can('edit_posts');
}
));
}
add_action('init', 'myprefix_register_meta');
Again, all of this is explained in the Css tricks tutorial.
I had the same problem - values were not being updated in the field and in the database - and, after some research, I have found that the reason for this is that you should add 'custom-fields' to the 'supports' array in your call to register_post_type(), like this:
register_post_type(
'my_post_type_slug',
array(
...
'supports' => array( 'title', 'editor', 'custom-fields' ),
...
)
);
You can test that this works by calling wp.data.select( 'core/editor' ).getCurrentPost().meta in your JavaScript console, when the block editor is loaded. If your post type does not add support for 'custom-fields', this call will return undefined; if it does, it will return an empty array (or rather, an array with the already existing meta from the database). This behavior is mentioned in the Gutenberg docs, in a note on registering your post meta:
To make sure the field has been loaded, query the block editor internal data structures, also known as stores. Open your browser’s console, and execute this piece of code:
wp.data.select( 'core/editor' ).getCurrentPost().meta;
Before adding the register_post_meta function to the plugin, this code returns a void array, because WordPress hasn’t been told to load any meta field yet. After registering the field, the same code will return an object containing the registered meta field you registered.
I worked on a similar implementation recently, and worked through a bunch of examples as well. Between the above-mentioned articles, and this great series by one of the Automattic devs, I got a working version of the above example using the newer useSelect and useDispatch custom hooks. It's really quite similar, but utilizes custom hooks from React 16.8 for a slightly more concise dev experience:
(Also, using #wordpress/scripts, so the imports are from the npm packages instead of the wp object directly, but either would work.)
import { __ } from '#wordpress/i18n';
import { useSelect, useDispatch } from '#wordpress/data';
import { PluginDocumentSettingPanel } from '#wordpress/edit-post';
import { TextControl } from '#wordpress/components';
const TextController = (props) => {
const meta = useSelect(
(select) =>
select('core/editor').getEditedPostAttribute('meta')['_myprefix_text_metafield']
);
const { editPost } = useDispatch('core/editor');
return (
<TextControl
label={__("Text Meta", "textdomain")}
value={meta}
onChange={(value) => editPost({ meta: { _myprefix_text_metafield: value } })}
/>
);
};
const PluginDocumentSettingPanelDemo = () => {
return (
<PluginDocumentSettingPanel
name="custom-panel"
title="Custom Panel"
className="custom-panel"
>
<TextController />
</PluginDocumentSettingPanel>
);
};
export default PluginDocumentSettingPanelDemo;
Hopefully that helps someone else searching.

GraphQL Directive requires its own query result as a variable. What is a smarter way to do this?

I've been in a feedback loop of trying to figure this out (new to GraphQL + Apollo). I got my directive "working" (the boolean does change the part of the fragment that is called, as intended). The problem is in order to write the expression that returns my directive variable, I need the resulting query that the directive is being called on! I don't know how to break this contradiction and am throwing the towel in in hopes that someone more experienced can point me in a better direction.
Here's the query and the hook it's being called on:
export const getDatasetPage = gql`
query dataset($datasetId: ID!, $editTrue: Boolean!) {
dataset(id: $datasetId) {
id
created
public
following
starred
...DatasetDraft //editTrue omits a piece of this fragment if user !hasEditPermissions
...DatasetPermissions
...DatasetSnapshots
...DatasetIssues
...DatasetMetadata
...DatasetComments
uploader {
id
name
email
}
analytics {
downloads
views
}
onBrainlife
}
}
${DatasetQueryFragments.DRAFT_FRAGMENT}
${DatasetQueryFragments.PERMISSION_FRAGMENT}
${DatasetQueryFragments.DATASET_SNAPSHOTS}
${DatasetQueryFragments.DATASET_ISSUES}
${DatasetQueryFragments.DATASET_METADATA}
${DATASET_COMMENTS}
`
// IDEAL CONDITION TO CHECK WRITE PERMISSIONS (won't work because dataset is required //prop...which is the result of the query)
// const editTrue = (dataset) => {
// const user = getProfile()
// return (user && user.admin) ||
// hasEditPermissions(dataset.permissions, user && user.sub)
// }
export const DatasetQueryHook = ({ datasetId, editTrue }) => {
const {
data: { dataset },
loading,
error,
} = useQuery(getDatasetPage, {
variables: { datasetId, editTrue },
})
if (loading) {
return <Spinner text="Loading Dataset" active />
} else {
if (error) Sentry.captureException(error)
return (
<ErrorBoundary error={error} subject={'error in dataset page'}>
<DatasetQueryContext.Provider
value={{
datasetId,
}}>
<DatasetPage dataset={dataset} />
</DatasetQueryContext.Provider>
</ErrorBoundary>
)
}
}
const DatasetQuery = ({ match }) => (
<ErrorBoundaryAssertionFailureException subject={'error in dataset query'}>
<DatasetQueryHook datasetId={match.params.datasetId} editTrue={editTrue} />
</ErrorBoundaryAssertionFailureException>
)
...
After reading tons of gql docs, I'm just frustrated that after finally managing to implement a directive on a piece of a fragment, I'm met with the challenge that the query itself produces a crucial piece of data that determines what that directive is.
To add some context, I'm basically trying to omit certain files being queried when a user doesn't have write permissions to them. Currently, they're being queried for all users despite not being displayed and thus causing hefty load times.

Resources