0

Dash app should allow user to upload an image from browser, followed by some operations using opencv, and then it needs to display modified image back. My problem is in update_output function. When I pass i, I see the image in the browser, however if I pass dataURI (modified version of image using opencv), then image is never displayed in a browser. Could someone help me please?

  #app = Dash(__name__, external_stylesheets=external_stylesheets)
  app.layout = html.Div([
      dcc.Upload(
          id='upload-image',
          children=html.Div([
              'Drag and Drop or ',
              html.A('Select Files')
          ]),
      ),
      html.Div(id='output-image-upload'),
      dcc.Store(id='store-data', storage_type='memory')
  ])
  
  @app.callback(Output('store-data', 'data'), Input('upload-image', 'contents') )
  
  def store_data(value): return value
  
  def data_uri_to_cv2_img(uri):
      encoded_data = uri.split(',')[1]
      nparr = np.fromstring(base64.b64decode(encoded_data), np.uint8)
      img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
      return img
  
  def parse_contents(contents):
      return html.Div([
          html.Img(src=contents, style={'height':'10%', 'width':'10%'})
      ])
  
  @app.callback(
      Output('output-image-upload', 'children'),
      Input('store-data', 'data')
  )
  
  def update_output(images):
      if not images:
          return
  
      children = []
      for i in images:
        img1 = data_uri_to_cv2_img(i)
        #some operations -- skip for now
        _, buffer = cv2.imencode('.jpg', img1)
        jpg_as_text = base64.b64encode(buffer.tobytes())
        dataURI = 'data:image/jpeg;base64,' + str(jpg_as_text) 
  
      children.append(parse_contents(dataURI))
      return children
  
  
  if __name__ == '__main__':
     app.run_server(host='localhost',port=8005, debug = True)
Christoph Rackwitz
  • 11,317
  • 4
  • 27
  • 36
elenaby
  • 167
  • 2
  • 11

1 Answers1

1

The issue you have is that the callback update_output() currently expects a list of images while its input is actually a single image : when the store_data() callback takes the content of an uploaded image, it puts that content straight into the store and overrides any data that were there previously.

If the goal is to append those images, you need to initialize the store with a data structured as a list and fix the first callback :

app.layout = html.Div([
    # ...
    dcc.Store(id='store-data', storage_type='memory', data=[])
])

# ...

@app.callback(
    Output('store-data', 'data'),
    Input('upload-image', 'contents'),
    State('store-data', 'data'))        # <- use State() to get current data
def store_data(value, data):
    if value is not None:
        data.append(value)
    return data

Also, not sure if it's the result of copy/pasting your code here but it appears you have an indentation issue in the second callback.

The other thing is that base64.b64encode() returns a binary string (ie. bytes) so you still need to convert those bytes back to ascii (otherwise the final dataURI would contain the binary sequence b'<data>' as is, like data:image/jpeg;base64,b'<data>') :

@app.callback(
    Output('output-image-upload', 'children'),
    Input('store-data', 'data')
)
def update_output(images):
    if not images:
        return

    children = []
    for i in images:
        img1 = data_uri_to_cv2_img(i)
        #some operations -- skip for now
        _, buffer = cv2.imencode('.jpg', img1)
        jpg_as_text = base64.b64encode(buffer.tobytes())
        dataURI = 'data:image/jpeg;base64,' + str(jpg_as_text, 'ascii')  # <- 

    children.append(parse_contents(dataURI))
    return children

EricLavault
  • 12,130
  • 3
  • 23
  • 45