a proposal to enhance collaboration
MXCuBE meeting @ Diamond Light Source, 1 February 2018
Minimalistic presentation by M. Guijarro
Collaboration from a developer perspective
People working altogether
Lots of interaction !
Technical debates
Shared knowledge
Everybody on the same boat
Integration of newcomers
Better quality software
MXCuBE collaboration
What do we share ? (from the MOU)
3 developers are pushing the vast majority of commits
New developers have difficulties to participate
Challenges ahead: test suite, Python 3, modernizing software architecture
Impediments to developers collaboration
Code Organization
User Interface
Web
Qt
Hardware Abstraction
2.2
master
MXCuBE 3
MXCuBE 2 dev.
MXCuBE 2.2
Hard to see the big picture, hard to make pull requests, hard to keep coherency
Code Organization: Hardware Abstraction
Missing abstract classes
80% of the code is specific, impossible to test
Code complexity
Hard to get into the code, hard to debug
No automatic testing: regression steps in !
Digression:
something to tell about MXCuBE 3 development
@mxcube.route("/mxcube/api/v0.1/queue/start", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/queue/stop", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/queue/abort", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/queue/pause", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/queue/unpause", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/queue/clear", methods=['PUT', 'GET'])
@mxcube.route("/mxcube/api/v0.1/queue", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/queue_state", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/queue/<sid>/<tindex>/execute", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/queue", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/queue", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/queue/<sqid>/<tqid>", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/queue/delete", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/queue/set_enabled", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/queue/<sid>/<ti1>/<ti2>/swap", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/queue/<sid>/<ti1>/<ti2>/move", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/queue/sample-order", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/queue/<sample_id>", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/queue/<node_id>/toggle", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/queue/dc", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/queue/char_acq", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/queue/char", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/queue/mesh", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/queue/<id>", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/queue/<sample_id>/<int:method_id>", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/queue/json", methods=["GET"])
@mxcube.route("/mxcube/api/v0.1/queue/automount", methods=["POST"])
@mxcube.route("/mxcube/api/v0.1/queue/num_snapshots", methods=["PUT"])
@mxcube.route("/mxcube/api/v0.1/queue/group_folder", methods=["POST"])
@mxcube.route("/mxcube/api/v0.1/queue/group_folder", methods=["GET"])
@mxcube.route("/mxcube/api/v0.1/queue/auto_add_diffplan", methods=["POST"])
MXCuBE 3 control API
@mxcube.route("/mxcube/api/v0.1/samples/<id>/collections/<colid>/mode", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/samples/<id>/collections/<colid>", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/samples/<id>/collections/<colid>", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/samples/<id>/collections/<colid>", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/samples/<id>/collections", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/samples/<id>/collections/<colid>", methods=['DELETE'])
@mxcube.route("/mxcube/api/v0.1/samples/<id>/collections/status", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/samples/<id>/collections/<colid>/status", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/samples/<sampleid>/collections/<colid>/run", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/beamline", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/beamline/<name>/abort", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/beamline/<name>/run", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/beamline/<name>", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/beamline/<name>", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/beam/info", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/beamline/datapath", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/beamline/prepare_beamline", methods=['PUT'])
MXCuBE 3 control API
@mxcube.route("/mxcube/api/v0.1/sample_changer/samples_list", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sample_changer/state", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sample_changer/loaded_sample", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sample_changer/contents", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sample_changer/select/<loc>", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sample_changer/scan/<loc>", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sample_changer/mount/<loc>", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sample_changer/unmount/<loc>", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sample_changer/unmount_current/", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sample_changer/mount", methods=["POST"])
@mxcube.route("/mxcube/api/v0.1/sample_changer/unmount", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/sample_changer/get_maintenance_cmds", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sample_changer/get_global_state", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sample_changer/get_initial_state", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sample_changer/send_command/<cmdparts>", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/login", methods=["POST"])
@mxcube.route("/mxcube/api/v0.1/signout")
@mxcube.route("/mxcube/api/v0.1/login_info", methods=["GET"])
@mxcube.route("/mxcube/api/v0.1/login/request_control", methods=["POST"])
@mxcube.route("/mxcube/api/v0.1/login/observers", methods=["GET"])
@mxcube.route("/mxcube/api/v0.1/login/request_control_response", methods=["POST"])
MXCuBE 3 control API
MXCuBE 3 control API
@mxcube.route("/mxcube/api/v0.1/sampleview/camera/subscribe", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sampleview/camera/unsubscribe", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/camera/save", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/camera", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sampleview/camera", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/sampleview/centring/<point_id>/moveto", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/shapes", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sampleview/shapes/<sid>", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sampleview/shape_mock_result/<sid>", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sampleview/shapes", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/sampleview/shapes/<sid>", methods=['DELETE'])
@mxcube.route("/mxcube/api/v0.1/sampleview/zoom", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/backlighton", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/backlightoff", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/frontlighton", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/frontlightoff", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/<motid>/<newpos>", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/<elem_id>", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/sampleview/centring/startauto", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/centring/start3click", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/centring/abort", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/centring/click", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/centring/accept", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/centring/reject", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/movetobeam", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/sampleview/centring/centring_method", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/diffractometer/phase", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/diffractometer/phaselist", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/diffractometer/phase", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/diffractometer/platemode", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/diffractometer/movables/state", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/diffractometer/aperture", methods=['PUT'])
@mxcube.route("/mxcube/api/v0.1/diffractometer/aperture", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/diffractometer/info", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/lims/samples/<proposal_id>", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/lims/dc/thumbnail/<image_id>", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/lims/dc/<dc_id>", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/lims/proposal", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/lims/proposal", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/workflow", methods=['GET'])
@mxcube.route("/mxcube/api/v0.1/workflow", methods=['POST'])
@mxcube.route("/mxcube/api/v0.1/workflow/dialog/<wf>", methods=['GET'])
MXCuBE 3 control API
Only 120 API functions are needed to have all features of MXCuBE
For completeness: 21 signals have to be emitted too
# diffractometer
socketio.emit('diff_phase_changed', data, namespace='/hwr')
# sample changer
socketio.emit('sc', msg, namespace='/hwr')
socketio.emit('sc_state', state_str, namespace='/hwr')
socketio.emit("loaded_sample_changed", {'address': address, 'barcode': barcode}, namespace="/hwr")
socketio.emit("set_current_sample", sample , namespace="/hwr")
socketio.emit("sc_contents_update")
socketio.emit("sc_maintenance_update", {'state': json.dumps(state_list), 'commands_state': json.dumps(cmd_state), 'message': message}, namespace="/hwr")
# sample centring
socketio.emit('sample_centring', msg, namespace='/hwr')
socketio.emit('update_shapes', {'shapes': shape_dict}, namespace='/hwr')
socketio.emit('update_pixels_per_mm', {"pixelsPerMm": ppm}, namespace='/hwr')
socketio.emit('beam_changed', {'data': ret}, namespace='/hwr')
# results and plotting
socketio.emit('grid_result_available', {'shape': shape}, namespace='/hwr')
socketio.emit('energy_scan_result', {'pk': pk, 'ip': ip, 'rm': rm}, namespace='/hwr')
socketio.emit("new_plot", plot_info, namespace="/hwr")
socketio.emit("plot_data", data, namespace="/hwr")
socketio.emit("plot_end", data, namespace="/hwr")
# beamline setup
socketio.emit('motor_position', movable, namespace='/hwr')
socketio.emit('motor_state', movable, namespace='/hwr')
socketio.emit("beamline_action", msg, namespace="/hwr")
socketio.emit("beamline_value_change", data, namespace="/hwr")
socketio.emit("mach_info_changed", values, namespace="/hwr")
Identification of MXCuBE base building blocks
Login
LIMS
Beamline setup
Sample Changer
Sample centring
Diffractometer
Queue
Data collection
External exp. control
Let's upgrade the Abstraction idea
Let's facilitate test/simulation
Current architecture
Qt UI
web UI
Hardware Objects
web backend
Hardware Objects
low-level beamline control
Moving to a higher-level abstraction
Qt UI
web UI
beamline control API
specific beamline control
Less is more
Much cleaner API for User Interfaces
Good use case for semantic version numbers
API documentation would be straightforward to write
Complete simulation environment is possible
Continuous Integration objective could be achieved
Open Questions
About the speaker...
This is not good-bye BUT...
please Steering Committee make sure MXCuBE has enough developers !