Skip to content

Commit

Permalink
[Snapshots] - New snapshot routine
Browse files Browse the repository at this point in the history
  • Loading branch information
walesch-yan authored and marcus-oscarsson committed Oct 1, 2024
1 parent ac22b9a commit 56b9e01
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 158 deletions.
4 changes: 3 additions & 1 deletion mxcubeweb/core/components/queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,7 @@ def _create_dc(self):
dc_model = qmo.DataCollection()
dc_model.set_origin(ORIGIN_MX3)
dc_model.center_before_collect = True
dc_model.take_snapshots = self.app.NUM_SNAPSHOTS
dc_entry = qe.DataCollectionQueueEntry(Mock(), dc_model)

return dc_model, dc_entry
Expand Down Expand Up @@ -2127,6 +2128,7 @@ def is_interleaved(self, node):

def init_queue_settings(self):
self.app.NUM_SNAPSHOTS = HWR.beamline.collect.get_property("num_snapshots", 4)
HWR.beamline.collect.number_of_snapshots = self.app.NUM_SNAPSHOTS
self.app.AUTO_MOUNT_SAMPLE = HWR.beamline.collect.get_property(
"auto_mount_sample", False
)
Expand Down Expand Up @@ -2387,7 +2389,7 @@ def get_default_task_parameters(self, task_name):
"inverse_beam": False,
"take_dark_current": True,
"skip_existing_images": False,
"take_snapshots": True,
"take_snapshots": self.app.NUM_SNAPSHOTS,
"helical": False,
"mesh": False,
"prefixTemplate": "{PREFIX}_{POSITION}",
Expand Down
117 changes: 0 additions & 117 deletions mxcubeweb/core/components/sampleview.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
# -*- coding: utf-8 -*-
import logging
import types
import sys
import os
import inspect

import PIL
import gevent.event

from flask import Response

from io import StringIO
import base64

from mxcubeweb.core.util.convertutils import to_camel, from_camel

Expand Down Expand Up @@ -118,12 +113,6 @@ def __init__(self, app, config):
self._centring_point_id = None
self.http_streamer = HttpStreamer()

enable_snapshots(
HWR.beamline.collect,
HWR.beamline.diffractometer,
HWR.beamline.sample_view,
)

HWR.beamline.sample_view.connect("shapesChanged", self._emit_shapes_updated)

zoom_motor = HWR.beamline.diffractometer.get_object_by_role("zoom")
Expand Down Expand Up @@ -509,109 +498,3 @@ def set_centring_method(self, method):
self.app.CENTRING_METHOD = CENTRING_METHOD.MANUAL

logging.getLogger("user_level_log").info(msg)


def enable_snapshots(collect_object, diffractometer_object, sample_view):
def _snapshot_received(data):
snapshot_jpg = data.get("data", "")

global SNAPSHOT
SNAPSHOT = base64.b64decode(snapshot_jpg)
SNAPSHOT_RECEIVED.set()

def _do_take_snapshot(filename, bw=False):
sample_view.save_snapshot(filename, overlay=False, bw=bw)

def save_snapshot(self, filename, bw=False):
sample_view.save_snapshot(filename, overlay=False, bw=bw)
# _do_take_snapshot(filename, bw)

def take_snapshots(self, snapshots=None, _do_take_snapshot=_do_take_snapshot):
from mxcubeweb.app import MXCUBEApplication as mxcube

if snapshots is None:
# called via AbstractCollect
dc_params = self.current_dc_parameters
move_omega_relative = diffractometer_object.move_omega_relative
else:
# called via AbstractMultiCollect
# calling_frame = inspect.currentframe()
calling_frame = inspect.currentframe().f_back.f_back

dc_params = calling_frame.f_locals["data_collect_parameters"]
move_omega_relative = diffractometer_object.phiMotor.set_value_relative

if dc_params["take_snapshots"]:
# The below does not work. NUM_SNAPSHOTS needs to e got in somehow
number_of_snapshots = mxcube.NUM_SNAPSHOTS
else:
number_of_snapshots = 0

if number_of_snapshots > 0:
if (
hasattr(diffractometer_object, "set_phase")
and diffractometer_object.get_current_phase() != "Centring"
):
use_custom_snapshot_routine = diffractometer_object.get_property(
"custom_snapshot_script_dir", None
)
if not use_custom_snapshot_routine:
logging.getLogger("user_level_log").info(
"Moving Diffractometer to CentringPhase Not done for tests (DN)"
)

diffractometer_object.set_phase("Centring", wait=True, timeout=200)

snapshot_directory = dc_params["fileinfo"]["archive_directory"]
if not os.path.exists(snapshot_directory):
try:
self.create_directories(snapshot_directory)
except Exception:
logging.getLogger("MX3.HWR").exception(
"Collection: Error creating snapshot directory"
)

logging.getLogger("user_level_log").info(
"Taking %d sample snapshot(s)" % number_of_snapshots
)

for snapshot_index in range(number_of_snapshots):
snapshot_filename = os.path.join(
snapshot_directory,
"%s_%s_%s.snapshot.jpeg"
% (
dc_params["fileinfo"]["prefix"],
dc_params["fileinfo"]["run_number"],
(snapshot_index + 1),
),
)
dc_params[
"xtalSnapshotFullPath%i" % (snapshot_index + 1)
] = snapshot_filename

try:
logging.getLogger("MX3.HWR").info(
"Taking snapshot number: %d" % (snapshot_index + 1)
)
_do_take_snapshot(snapshot_filename)
# diffractometer.save_snapshot(snapshot_filename)
except Exception as ex:
sys.excepthook(*sys.exc_info())
raise RuntimeError(
"Could not take snapshot '%s'",
snapshot_filename,
) from ex

if number_of_snapshots > 1:
move_omega_relative(90)
diffractometer_object.wait_ready()

collect_object.take_crystal_snapshots = types.MethodType(
take_snapshots, collect_object
)

diffractometer_object.save_snapshot = types.MethodType(
save_snapshot, diffractometer_object
)

sample_view.set_ui_snapshot_cb(save_snapshot)
14 changes: 6 additions & 8 deletions mxcubeweb/core/components/user/usermanager.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
import json
import uuid
import datetime
import requests
Expand All @@ -23,14 +22,13 @@
class BaseUserManager(ComponentBase):
def __init__(self, app, config):
super().__init__(app, config)

self.oauth_client = OAuth(app=app.server.flask)
self.oauth_issuer = "https://websso.esrf.fr/realms/ESRF/"
self.oauth_logout_url = (
"https://websso.esrf.fr/auth/realms/ESRF/protocol/openid-connect/logout"
)
self.oauth_client_secret = "95nOugpRxwF3ttXxYnXFiK6bou5wtSP1"
self.oauth_client_id = "mxcube"

self.oauth_issuer = self.app.CONFIG.sso.ISSUER
self.oauth_logout_url = self.app.CONFIG.sso.LOGOUT_URI
self.oauth_client_secret = self.app.CONFIG.sso.CLIENT_SECRET
self.oauth_client_id = self.app.CONFIG.sso.CLIENT_ID

self.oauth_client.register(
name="keycloak",
client_id=self.oauth_client_id,
Expand Down
1 change: 1 addition & 0 deletions mxcubeweb/routes/queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ def set_autmount():
def set_num_snapshots():
data = request.get_json()
app.NUM_SNAPSHOTS = data.get("numSnapshots", 4)
HWR.beamline.collect.number_of_snapshots = app.NUM_SNAPSHOTS
resp = jsonify({"numSnapshots": data.get("numSnapshots", 4)})
resp.status_code = 200

Expand Down
30 changes: 18 additions & 12 deletions mxcubeweb/routes/samplecentring.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import os
import json

from flask import Blueprint, Response, jsonify, request
from flask import Blueprint, Response, jsonify, request, send_file

from mxcubecore import HardwareRepository as HWR

Expand Down Expand Up @@ -35,22 +34,29 @@ def unsubscribe_to_camera():
HWR.beamline.sample_view.camera.streaming_greenlet.kill()
return Response(status=200)

@bp.route("/camera/save", methods=["PUT"])
@bp.route("/camera/snapshot", methods=["POST"])
@server.restrict
def snapshot():
"""
Save snapshot of the sample view
data = {generic_data, "Path": path} # not sure if path should be available,
or directly use the user/proposal path
Return: 'True' if command issued succesfully, otherwise 'False'.
Take snapshot of the sample view
data = {"overlay": overlay_data} overlay is the image data to overlay on sample image,
it should normally contain the data of shapes drawn on canvas.
Return: Overlayed image uri, if successful, statuscode 500 otherwise.
"""
try:
HWR.beamline.sample_view.camera.takeSnapshot(
os.path.join(os.path.dirname(__file__), "snapshots/")
overlay = json.loads(request.data).get("overlay")
snapshot_data_uri = HWR.beamline.sample_view.take_snapshot(
overlay_data=overlay,
)
return "True"
except Exception:
return "False"
snapshot_data_uri.seek(0)
return send_file(
snapshot_data_uri,
mimetype="image/jpeg",
as_attachment=True,
download_name="test.png",
)
except Exception as e:
return jsonify({"error": str(e)}), 500

@bp.route("/camera", methods=["GET"])
@server.restrict
Expand Down
8 changes: 4 additions & 4 deletions test/input_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@
"subDirTemplate": "{ACRONYM}/{ACRONYM}-{NAME}",
"sub_wedge_size": 10,
"take_dark_current": True,
"take_snapshots": True,
"take_snapshots": 4,
"take_video": False,
"transmission": 10,
},
Expand Down Expand Up @@ -241,7 +241,7 @@
"subDirTemplate": "{ACRONYM}/{ACRONYM}-{NAME}",
"sub_wedge_size": 10,
"take_dark_current": True,
"take_snapshots": True,
"take_snapshots": 4,
"take_video": False,
"transmission": 10,
"use_aimed_multiplicity": False,
Expand Down Expand Up @@ -297,7 +297,7 @@
"subDirTemplate": "{ACRONYM}/{ACRONYM}-{NAME}",
"sub_wedge_size": 10,
"take_dark_current": True,
"take_snapshots": True,
"take_snapshots": 4,
"take_video": False,
"transmission": 10,
},
Expand Down Expand Up @@ -349,7 +349,7 @@
"subDirTemplate": "{ACRONYM}/{ACRONYM}-{NAME}",
"sub_wedge_size": 10,
"take_dark_current": True,
"take_snapshots": True,
"take_snapshots": 4,
"take_video": False,
"transmission": 10,
},
Expand Down
4 changes: 4 additions & 0 deletions ui/src/api/sampleview.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,7 @@ export function sendMoveToBeam(x, y) {
export function sendUpdateMotorPosition(motorName, value) {
return endpoint.put(undefined, `/${motorName}/${value}`).res();
}

export function sendTakeSnapshot(canvasData) {
return endpoint.post({ overlay: canvasData }, '/camera/snapshot').blob();
}
29 changes: 13 additions & 16 deletions ui/src/components/SampleView/SampleControls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import 'fabric';

import OneAxisTranslationControl from '../MotorInput/OneAxisTranslationControl';
import { MOTOR_STATE } from '../../constants';
import { sendTakeSnapshot } from '../../api/sampleview';

import { find } from 'lodash';
import styles from './SampleControls.module.css';
import { downloadImage } from './utils';

const { fabric } = window;

Expand All @@ -19,7 +21,7 @@ export default class SampleControls extends React.Component {
super(props);

this.takeSnapShot = this.takeSnapShot.bind(this);
this.doTakeSnapshot = this.doTakeSnapshot.bind(this);
this.getCanvasData = this.getCanvasData.bind(this);
this.toggleFrontLight = this.toggleLight.bind(
this,
'diffractometer.frontlight',
Expand All @@ -33,10 +35,6 @@ export default class SampleControls extends React.Component {
this.availableVideoSizes = this.availableVideoSizes.bind(this);
}

componentDidMount() {
window.takeSnapshot = this.doTakeSnapshot;
}

toggleDrawGrid() {
// Cancel click centering before draw grid is started
if (this.props.currentSampleID === '') {
Expand All @@ -50,20 +48,24 @@ export default class SampleControls extends React.Component {
}
}

doTakeSnapshot() {
getCanvasData() {
const img = document.querySelector('#sample-img');
const fimg = new fabric.Image(img);
fimg.scale(this.props.imageRatio);
let imgDataURI = '';
this.props.canvas.setBackgroundImage(fimg);
this.props.canvas.renderAll();
imgDataURI = this.props.canvas.toDataURL({ format: 'jpeg' });
imgDataURI = this.props.canvas.toDataURL({
format: 'png',
backgroundColor: null,
});
this.props.canvas.setBackgroundImage(0);
this.props.canvas.renderAll();
return { data: imgDataURI.slice(23), mime: imgDataURI.slice(0, 23) };
// this function should only return the data not the mime
return imgDataURI.split(',')[1];
}

takeSnapShot(evt) {
async takeSnapShot() {
/* eslint-disable unicorn/consistent-function-scoping */
function imageEpolog(props) {
const { sampleID } = props.currentSampleID;
Expand All @@ -76,11 +78,10 @@ export default class SampleControls extends React.Component {
return 'no-sample';
}

const img = this.doTakeSnapshot();
const processedImgBlob = await sendTakeSnapshot(this.getCanvasData());
const filename = `${this.props.proposal}-${imageEpolog(this.props)}.jpeg`;

evt.currentTarget.href = img.mime + img.data;
evt.currentTarget.download = filename;
downloadImage(processedImgBlob, filename);
}

toggleCentring() {
Expand Down Expand Up @@ -170,11 +171,7 @@ export default class SampleControls extends React.Component {
<div className={styles.controls}>
{this.props.getControlAvailability('snapshot') && (
<Button
as="a"
className={styles.controlBtn}
href="#"
target="_blank"
download
title="Take snapshot"
data-toggle="tooltip"
onClick={this.takeSnapShot}
Expand Down
10 changes: 10 additions & 0 deletions ui/src/components/SampleView/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function downloadImage(blob, download_name) {
const url = window.URL.createObjectURL(new Blob([blob]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', download_name);
document.body.append(link);
link.click();
link.remove();
URL.revokeObjectURL(url);
}

0 comments on commit 56b9e01

Please sign in to comment.