plom.client
module
Plom Client
Plom client and supporting functions.
- class plom.client.Chooser(Qapp, webplom=False)[source]
-
- partial_parse_address()[source]
If address has a port number in it, extract and move to the port box.
If there’s a colon in the address (maybe user did not see port entry box or is pasting in a string), then try to extract a port number and put it into the entry box.
In some rare cases, we actively clear the port box, for example when the URL seems to have a path.
- class plom.client.IDClient(Qapp, tmpdir=None)[source]
Initialize the Identifier Client.
- Parameters:
Qapp (QApplication) – Main client application
tmpdir (pathlib.Path/str/None) – a temporary directory for storing image files and other data. In principle can be shared with Marker although this may not be implemented. If None, we will make our own.
- enterID()[source]
Triggered when user hits return in the ID-lineedit.. that is when they have entered a full student ID.
- getClassList()[source]
Get the classlist from the server.
Here and throughout ‘snid’ means “student_id_and_name” as one string.
Returns nothing but modifies the state of self, adding three dicts to the class data:
snid_to_student_id snid_to_student_name student_id_to_snid
- Raises:
PlomNoClasslist –
- getPredictions()[source]
Send request for prediction list to server.
For some reason, this also updates font-sizes and stuff.
- identifyStudent(index, sid, sname, blank=False, no_id=False)[source]
Push identification of a paper to the server and misc UI table.
User ID’s the student of the current paper. Some care around whether or not the paper was ID’d previously. Not called directly - instead is called by “enterID” or “acceptPrediction” when user hits return on the line-edit.
- Parameters:
index – an index into the UI table of the currently highlighted row.
sname (str) – The student name or special placeholder. - note that this should always be non-trivial string.
sid (str/None) – The student ID or None. - note that this is either ‘None’ (but only if blank or no_id is true), or should have passed the ‘is_valid_id’ test.
blank (bool) – the paper was blank: sid must be None and sname must be “Blank paper”.
no_id (bool) – paper is not blank but student did not fill-in the ID page(s). sid must be None and sname must be “No ID given”.
- Returns:
True on success, False/None on failure.
- Return type:
True/False/None
- requestNext()[source]
Ask the server for an unID’d paper. Get file, add to the list of papers and update the image.
- setCompleters()[source]
Set up the studentname + studentnumber line-edit completers. Means that user can enter the first few numbers (or letters) and be prompted with little pop-up with list of possible completions.
- class plom.client.MarkerClient(Qapp, tmpdir=None)[source]
Setup for marking client and annotator
Notes
TODO: should be a QMainWindow but at any rate not a Dialog TODO: should this be parented by the QApplication?
Initialize a new MarkerClient
- Parameters:
Qapp (QApplication) – Main client application
tmpdir (pathlib.Path/str/None) – a temporary directory for storing image files and other data. In principle can be shared with Identifier although this may not be implemented. If None, we will make our own.
- UIInitialization()[source]
Startup procedure for the user interface
- Returns:
Modifies self.ui
- Return type:
None
- applyLastTimeOptions(lastTime)[source]
Applies all settings from previous client.
- Parameters:
lastTime (dict) – information about settings, often from a
run. (config file such as from the last time the client was) –
- Returns:
None
- backgroundUploadFailed(task, errmsg)[source]
An upload has failed, we don’t know why, do something LOUDLY.
- Parameters:
task (str) – the task ID of the current test.
errmsg (str) – the error message.
- Returns:
None
- backgroundUploadFailedServerChanged(task, error_message)[source]
An upload has failed because server changed something, safest to quit.
- Parameters:
task (str) – the task ID of the current test.
error_message (str) – a brief description of the error.
- Returns:
None
- backgroundUploadFinished(task, numDone, numtotal)[source]
An upload has finished, do appropriate UI updates
- Parameters:
task (str) – the task ID of the current test.
numDone (int) – number of exams marked
numTotal (int) – total number of exams to mark.
- Returns:
None
- callbackAnnDoneCancel(task)[source]
Called when anotator is done grading.
- Parameters:
task (str) – task name
- Returns:
None
- callbackAnnDoneClosing(task)[source]
Called when annotator is done grading and is closing.
- Parameters:
task (str) – the task ID of the current test.
- Returns:
None
- callbackAnnWantsUsToUpload(task, stuff)[source]
Called when annotator wants to upload.
- Parameters:
task (str) – the task ID of the current test.
stuff (list) – a list containing grade(int): grade given by marker. markingTime(int): total time spent marking. paperDir(dir): Working directory for the current task aname(str): annotated file name plomFileName(str): the name of the .plom file rubric(list[str]): the keys of the rubrics used integrity_check(str): the integrity_check string of the task.
- Returns:
None
- claim_task_and_trigger_downloads(task)[source]
Claim a particular task for the current user and start image downloads.
Notes
Side effects: on success, updates the table of tasks by adding a new row. The new row is not automatically selected.
- Returns:
None
- Raises:
PlomTakenException –
PlomVersionMismatchException –
- connectGuiButtons()[source]
Connect gui buttons to appropriate functions
Notes
TODO: remove the total-radiobutton
- Returns:
None - Modifies self.ui
- ensureAllDownloaded(new, old)[source]
Whenever the selection changes, ensure downloaders are either finished or running for each image.
We might need to restart downloaders if they have repeatedly failed. Even if we are still waiting, we can signal to the download the we have renewed interest in this particular download. TODO: for example. maybe we should send a higher priority? No: currently this also happens “in the background” b/c Marker selects the new row.
- Parameters:
new (QItemSelection) – the newly selected cells.
old (QItemSelection) – the previously selected cells.
- Returns:
None
- getDataForAnnotator(task)[source]
Start annotator on a particular task.
- Parameters:
task (str) – the task id. If original qXXXXgYY, then annotated version is GXXXXgYY (G=graded).
- Returns:
as described by startTheAnnotator, if successful.
- Return type:
list/None
- getMorePapers(oldtgvID)[source]
Loads more tests.
- Parameters:
oldtgvID (str) – the Test-Group-Version ID for the previous test.
- Returns:
as described by getDataForAnnotator
- Return type:
initialData
- getRubricsFromServer(question=None)[source]
Get list of rubrics from server.
- Parameters:
question (int/None) –
- Returns:
A list of the dictionary objects.
- Return type:
list
- get_downloads_for_src_img_data(src_img_data, trigger=True)[source]
Make sure the images for some source image data are downloaded.
If an image is not yet downloaded, trigger the download again.
- Parameters:
src_img_data (list) – list of dicts. Note we may modify this so pass a copy if you don’t want this! Specifically, the
"filename"
key is inserted or replaced with the path to the downloaded image or the placeholder image.- Keyword Arguments:
trigger (bool) – if True we trigger background jobs for any that have not been downloaded.
- Returns:
True if all images have already been downloaded, False if at least one was not. In the False case, downloads have been triggered; wait; process events; then call back if you want.
- Return type:
bool
- get_file_for_previous_viewer(task)[source]
Get the annotation file for the given task. Check to see if the local system already has the files for that task and if not grab them from the server. Then pass the annotation-image-file back to the caller.
- get_files_for_previously_annotated(task)[source]
Loads the annotated image, the plom file, and the original source images.
- Parameters:
task (str) – the task for the image files to be loaded from. Takes the form “q1234g9” = test 1234 question 9
- Returns:
currently this returns True. Unless it fails which will induce a crash (after some popup dialogs).
- Return type:
bool
- Raises:
Uses error dialogs; not currently expected to throw exceptions –
- get_upload_queue_length()[source]
How long is the upload queue?
An overly long queue might be a sign of network troubles.
- Returns:
The number of papers waiting to upload, possibly but not certainly including the current upload-in-progress. Value might also be approximate.
- Return type:
int
- latexAFragment(txt, *, quiet=False, cache_invalid=True, cache_invalid_tryagain=False)[source]
Run LaTeX on a fragment of text and return the file name of a png.
The files are cached for reuse if the same text is passed again.
- Parameters:
txt (str) – the text to be Latexed.
- Keyword Arguments:
quiet (bool) – if True, don’t popup dialogs on errors. Caution: this can result in a lot of API calls because users can keep requesting the same (bad) TeX from the server, e.g., by having bad TeX in a rubric.
cache_invalid (bool) – whether to cache invalid TeX. Useful to prevent repeated calls to render bad TeX but might prevent users from seeing (again) an error dialog that
try_again_if_cache_invalid (bool) – if True then when we get a cache hit of None (corresponding to bad TeX) then we try to to render again.
- Returns:
a path and filename to a
.png
of the rendered TeX. Or None if there was an error: callers will need to decide how to handle that, typically by displaying the raw code instead.- Return type:
pathlib.Path/str/None
- loadMarkedList()[source]
Loads the list of previously marked papers into self.examModel
- Returns:
None
- manage_task_tags(task, parent=None)[source]
Manage the tags of a task.
- Parameters:
task (str) – A string like “q0003g2” for paper 3 question 2.
- Keyword Arguments:
parent (Window/None) – Which window should be dialog’s parent? If None, then use self (which is Marker) but if other windows (such as Annotator or PageRearranger) are calling this and if so they should pass themselves: that way they would be the visual parents of this dialog.
- moveToNextUnmarkedTest(task=None)[source]
Move the list to the next unmarked test, if possible.
- Parameters:
task (str) – the task number of the next unmarked test.
- Returns:
True if move was successful, False if not, for any reason.
- property prefer_above
User prefers to mark papers above this value, or None if no preference.
- requestInteractive()[source]
Ask user for paper number and then ask server for that paper.
If available, download stuff, add to list, update view.
- requestNext(*, update_select=True)[source]
Ask server for an unmarked paper, get file, add to list, update view.
Retry a few times in case two clients are asking for same.
- Keyword Arguments:
update_select (bool) – default True, send False if you don’t want to adjust the visual selection.
- Returns:
None
- setup(messenger, question, version, lastTime)[source]
Performs setup procedure for markerClient.
TODO: move all this into init?
TODO: verify all lastTime Params, there are almost certainly some missing
- Parameters:
messenger (Messenger) – handle communication with server.
question (int) – question number.
version (int) – version number
lastTime (dict) –
settings. containing:
{ "POWERUSER" "FOREGROUND" "CommentsWarnings" "MarkWarnings" "KeyBinding" }
and potentially others
- Returns:
None
- startTheAnnotator(initialData)[source]
This fires up the annotation window for user annotation + marking.
- Parameters:
initialData (list) – containing things documented elsewhere in
plom.client.annotator.Annotator.__init__()
.- Returns:
None
- updatePreviewImage(new, old)[source]
Updates the displayed image when the selection changes.
- Parameters:
new (QItemSelection) – the newly selected cells.
old (QItemSelection) – the previously selected cells.
- Returns:
None
- class plom.client.annotator.Annotator(username, parentMarkerUI=None, initialData=None)[source]
The main annotation window for annotating group-images.
A subclass of QWidget
Initializes a new annotator window.
- Parameters:
username (str) – username of Marker
parentMarkerUI (MarkerClient) – the parent of annotator UI.
initialData (dict) – as documented by the arguments to “load_new_question”
- addImageMode()[source]
Opens a file dialog for images, shows a message box if the image is too large, otherwise continues to image mode.
Notes
If the Image is greater than 200kb, will return an error.
- Returns:
None
- changeCBZoom(CBIndex)[source]
Keeps zoom combo box at selected index.
- Parameters:
CBIndex (int) – the current zoom Combo Box Index
- Returns:
Modifies self.ui
- Return type:
None
- change_annot_scale(scale=None)[source]
Change the scale of the annotations.
- Parameters:
scale (float/None) – if None reset the scale to the default. If any floating point number, multiple the scale by that value.
- closeEvent(event)[source]
Overrides QWidget.closeEvent().
Deal with various cases of window trying to close.
Notes: These include:
User closes window via titlebar close icon (or alt-f4 or…)
User clicks “Cancel”
User clicks “Done”
Window close or Cancel are currently treated the same way: discard all annotations.
- Parameters:
event – the event of the window closing.
- Returns:
modifies many instance vars.
- Return type:
None
- close_current_question()[source]
Closes the current question, closes scene and clears instance vars.
Notes
As a result of this method, many instance variables will be None. Be cautious of how these variables will be handled in cases where they are None.
- Returns:
Modifies self.
- Return type:
None
- close_current_scene()[source]
Removes the current cene, saving some info in case we want to open a new one.
- Returns:
Modifies self.
- Return type:
None
- get_nonrubric_text_from_page()[source]
Retrieves text (not in rubrics) from the scene.
- Returns:
strings for text annotations not in a rubric.
- Return type:
list
- handleRubric(rubric)[source]
Pass a rubric dict onward to the scene, if we have a scene.
- Parameters:
rubric (dict) – we don’t care what’s in it: that’s for the scene and the rubric widget to agree on!
- Returns:
Modifies self.scene
- Return type:
None
- isZoomFitHeight()[source]
Sets the zoom ui text when user has selected “Fit Height.”
- Returns:
Modifies self.ui
- Return type:
None
- isZoomFitWidth()[source]
Sets the zoom ui text when user has selected “Fit Width.”
- Returns:
Modifies self.ui
- Return type:
None
- is_dirty()[source]
Is the scene dirty?
Has the scene been annotated or changed this session? Re-opening a previous annotated scene does not dirty it, until changes are made. Changes could be made and then undone back to the clean state. The concept should be familiar to “file saved” in a text editor.
- keyPopUp(*, tab_idx=None)[source]
View help and keyboard shortcuts, eventually edit them.
- Keyword Arg:
- tab_idx (int/None): which tab to open in the help. If None
then we try to re-open on the same tab from last run.
- keyToChangeRubric(keyNumber)[source]
Translates a the numerical key into a selection of that visible row of the current rubric tab.
- Returns:
modifies self.rubric_widget
- Return type:
None
- load_new_question(tgvID, question_label, version, max_version, testName, paperdir, saveName, maxMark, plomDict, integrity_check, src_img_data)[source]
Loads new data into the window for marking.
- Parameters:
tgvID (str) – Test-Group-Version ID code. For example, for Test #0027, group #13, version #2, we have t0027g13v2. TODO: currently only t0027g13, no version, despite name.
question_label (str) – The name of the question we are marking. This is generally used for display only as there is an integer for precise usage.
version (int) – which version are we working on?
max_version (int) – what is the largest version in this assessment?
testName (str) – Test Name
paperdir (dir) – Working directory for the current task
saveName (str/pathlib.Path) – file name (and dir, optionally) of the basename to save things (no .png/.jpg extension) If it does have an extension, it will be ignored.
maxMark (int) – maximum possible score for that test question
plomDict (dict) – a dictionary of annotation information. Contains sufficient information to recreate the annotation objects on the page if you go back to continue annotating a question.
integrity_check (str) – integrity check string
src_img_data (list[dict]) – image md5sums, filenames etc.
- Returns:
Modifies many instance vars.
- Return type:
None
- narrowLayout()[source]
Changes view to narrow Layout style.
- Returns:
modifies self.ui
- Return type:
None
- new_or_permuted_image_data(src_img_data)[source]
We have permuted/added/removed underlying source images, tear done and build up again.
- next_minor_tool(dir=1, always_move=False)[source]
Switch to current minor tool or advance to next minor tool.
- Parameters:
dir (int) – +1 for next (default), -1 for previous.
always_move (bool) – the minor tools keep track of the last-used tool. Often, but not always, we want to switch back to the last-used tool. False by default.
- pickleIt()[source]
Capture the annotated pages as a bitmap and a .plom file.
Renders the current scene as a static bitmap.
Retrieves current annotations in reverse chronological order.
Adds various other metadata.
Writes JSON into the
.plom
file.
Note: called “pickle” for historical reasons: it is neither a Python pickle nor a real-life pickle.
- Returns:
two pathlib.Path, one for the rendered image and one for the
.plom
file.- Return type:
tuple
- refreshDisplayedMark(score)[source]
Update the marklabel (and narrow one) with the current score - triggered by pagescene
- Returns:
None
- saveAndClose()[source]
Save the current annotations, and then close.
- Returns:
alters self.scene
- Return type:
None
- saveAnnotations()[source]
Try to save the annotations and signal Marker to upload them.
Notes
There are various sanity checks and user interaction to be done. Return False if user cancels. Return True if we should move on (for example, to close the Annotator).
Be careful of a score of 0 - when mark total or mark up. Be careful of max-score when marking down. In either case - get user to confirm the score before closing. Also confirm various “not enough feedback” cases.
- Returns:
False if user cancels, True if annotator is closed successfully.
- saveWindowSettings()[source]
Saves current window settings and other state into the parent.
- Returns:
modifies self.parentMarkerUI and self.scene
- Return type:
None
- setAllIcons()[source]
Sets all icons for the ui Tool Buttons.
- Returns:
Modifies ui Tool Buttons.
- Return type:
None
- setIcon(toolButton, name, iconfile)[source]
Sets a name and svg icon for a given QToolButton.
- Parameters:
toolButton (QToolButton) – the ui Tool Button for a name and icon to be added to.
name (str) – a name defining toolButton.
iconfile (str) – filename of .svg, must be in the resource plom.client.icons.
- Returns:
alters toolButton
- Return type:
None
- setMinorShortCuts()[source]
Setup non-editable shortcuts.
Each of these actions can be associated with multiple shortcut keys.
- setToolMode(newMode, *, cursor=None, imagePath=None)[source]
Changes the current tool mode and cursor.
- Parameters:
newMode (str) –
"move"
,"rubric"
etc.- Keyword Arguments:
imagePath – an argument for the “image” tool, used used only by the image tool.
cursor (str) – if None or omitted default cursors are used for each tool. If needed you could override this. (currently unused, semi-deprecated).
Notes
TODO: this does various other mucking around for legacy reasons: could probably still use some refactoring.
- Returns:
Modifies self
- Return type:
None
- setToolShortCuts()[source]
Set or change the shortcuts for the basic tool keys.
These are the shortcuts that are user-editable.
- setViewAndScene(src_img_data)[source]
Makes a new scene (pagescene object) and connects it to the view (pageview object).
The pageview (which is a qgraphicsview) which is (mostly) a layer between the annotation widget and the graphics scene which actually stores all the graphics objects (the image, lines, boxes, text etc etc). The view allows us to zoom pan etc over image and its annotations.
- Returns:
modifies self.scene from None to a pagescene object and connects it to a pageview object.
- Return type:
None
- setZoomComboBox()[source]
Sets the combo box for the zoom method.
- Returns:
Modifies self.ui
- Return type:
None
- swapMaxNorm()[source]
Toggles the window size between max and normal.
- Returns
None: modifies self.windowState
- toggleTools()[source]
Shows/Hides tools making more space to view the group-image.
- Returns:
modifies self.ui.hideableBox
- Return type:
None
- unpickleIt(plomData)[source]
Unpickles the page by calling scene.unpickleSceneItems and sets the page’s mark.
- Parameters:
plomData (dict) – a dictionary containing the data for the pickled .plom file.
- Returns:
None
Update the menu which shows the current annotation scale.
- viewWholePaper()[source]
Popup a dialog showing the entire paper.
TODO: this has significant duplication with RearrangePages.
- Returns:
None
The background downloader downloads images using threads.
- class plom.client.downloader.DownloadWorker(msgr, img_id, md5, target_name, *, basedir, simulate_failures=False)[source]
- class plom.client.downloader.Downloader(basedir, *, msgr=None)[source]
Downloads and maintains a cache of images.
Downloader maintains a queue of downloads and emits signals whenever downloads succeed or fail.
Call
download_in_background_thread()
to enqueue an image for asynchronous download. Once enqueued, a download will be automatically retried several times, but to prevent endless data usage, it will give up after three tries. That is, clients cannot assume that something enqueued will inevitably be downloaded. Clients can check by TODO: document how to check if something is in the queue or/and or currently downloading.Synchronous downloads can be performed with
sync_download()
andsync_downloads()
. These images will also be cached.TODO: document how to query the queue size. TODO: document how to query the size on disc.
The current queue can be cleared with
clear_queue()
. For shutting down the queue, seestop()
. The Downlaoder keeps a clone of the messenger: if you logout (revoke the token) in another msgr while this is downloading, you’ll get a crash.The Downloader will emit various signals. You can connect slots to these:
download_finished(img_id: int, md5sum: str, filename: str): emitted when a (background) download finishes. filename is newly-downloaded file.
download_failed(img_id: int): emitted when a (background) download fails. The job will be automatically restarted up to three times.
download_queue_changed(dict): the queue length changed (e.g., something enqueued or the queue is cleared). The signal argument is a dict of information about the queue.
Use
enable_fail_mode()
to artificially fail some download attempts and generally take longer. For debugging. Disable again withdisable_fail_mode()
.Initialize a new Downloader.
- Parameters:
basedir (pathlib.Path/str) – a directory for the image cache.
- Keyword Arguments:
msgr (Messenger) – Note Messenger is not multithreaded and blocks using mutexes. Here we make our own private clone so caller can keep using their’s.
- clear_queue()[source]
Cancel any enqueued (but not yet started) downloads.
Any existing downloads will continue, including their (up-to) three retries.
- download_in_background_thread(row, priority=False, _is_retry=False)[source]
Enqueue the downloading of particular row of the image database.
- Parameters:
row (dict) – One image entry in the “page data”, has fields id, md5 and some others that are used to try to choose a reasonable local file name. Currently the local file name is chosen from the
"server_path"
key.- Keyword Arguments:
priority (bool) – high priority if user requested this (not a background download.
_is_retry (bool) – default False. If True, this signifies an automatic retry. Clients should probably not touch this.
Does not start a new download if the Page Cache already has that image. It also tries to avoid enquing another request for the same image.
- get_placeholder_path()[source]
A static image that can be used as a placeholder while images are downloading.
- Returns:
- a real path on disc to the image, possibly a cached
copy. But for now its a str (TODO?).
- Return type:
pathlib.Path
Currently you may have to make a string (not an Path for example) b/c of some Qt limitations in the ExamModel and proxy stuff in Marker.
TODO: Issue #2357: better image or perhaps an animation?
- stop(timeout=- 1)[source]
Try to stop the downloader, after waiting for threads to clear.
- Parameters:
timeout (int) – milliseconds seconds to wait before giving up.
-1
to wait forever.- Returns:
True if all threads finished or False if timeout reached.
- Return type:
bool
- sync_download(row)[source]
Given a row of “pagedata”, download synchronously and return edited row.
- Parameters:
row (dict) – one row of the metadata for the set of all pages involved in a question. A list of dicts where each dict must have (at least) keys
id
,md5
,server_path
. TODO: sometimes we seem to acceptmd5sum
instead: should fix that.- Returns:
the modified row. If the file was already downloaded, put its name into the
filename
key. If we had to download it we also put the filename intofilename
.- Return type:
dict
- sync_downloads(pagedata)[source]
Given a block of “pagedata” download all images synchronously and return updated data.
- Parameters:
pagedata (list) – a list of dicts, each dict described in sync_download. Warning: we don’t make a copy: it will be modified (and returned).
- Returns:
a list of dicts which consists of the updated input with filenames added/updated for each image.
- Return type:
list
- class plom.client.downloader.WorkerSignals[source]
Defines the signals available from a running worker thread.
Supported signals are:
- finished:
No data
- download_success:
(img_id (int), md5 (str), tempfile (str), targetfile (str)
- download_fail:
(img_id (int), md5 (str), targetfile (str), err_stuff_tuple (tuple) where the tuple is (exctype, value, traceback.format_exc().
plom.manager
module
Plom server management tools.
- class plom.manager.Manager(Qapp, *, server=None, user=None, password=None, manager_msgr=None)[source]
Plom server management and marking progress UI tool.
Start a new Plom Manager window.
- Parameters:
Qapp (QApplication) –
- Keyword Arguments:
manager_msgr (ManagerMessenger/None) – a connected ManagerMessenger. Note that the plain ‘ol Messenger will not work. By default or if None is passed, we’ll make the user login or use other kwargs.
server (str/None) –
user (str/None) –
password (str/None) –
- manage_task_tags(paper_num, question, parent=None)[source]
Manage the tags of a task.
- Parameters:
paper_num (int/str) –
question (int/str) –
- Keyword Arguments:
parent (Window/None) – Which window should be dialog’s parent? If None, then use self (which is Marker) but if other windows (such as Annotator or PageRearranger) are calling this and if so they should pass themselves: that way they would be the visual parents of this dialog.
- Returns:
the current tags of paper/question. Note even if the dialog is cancelled, this will be updated (as someone else could’ve changed tags).
- Return type:
list