-
Notifications
You must be signed in to change notification settings - Fork 458
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: ENH: allow direct execution of CLI python scripts using Slicer's python #894
Conversation
@ihnorton thank you for working on this! I am afraid that I don't know enough about the subject matter to give any helpful feedback, and can't allocate time to investigate in depth. |
Looks very promising but I also didn't have time to dig into it. |
Here is a little more context. The goal is to run python scripts outside Slicer, but using Slicer's python (and environment). SlicerRadiomicsCLI currently does this via batch files and shell scripts as wrappers used to re-invoke Slicer's python on a script (pieper's solution) -- but this has the limitations detailed in the linked forum thread due to security policy. We would like to do something similar for WhiteMatterAnalysis, and will face the same constraints, especially the need to run the computations asynchronously. There is a different approach via a custom launcher, but it would require a net addition of CMakeScript, and still require the batch and shell scripts wrapper layers. I think the approach here could be simpler for extension developers to maintain. The approach of this PR is to teach the CLI mechanism to run python scripts directly, which has two steps:
|
Thanks @ihnorton that makes sense now. Definitely cleaner to centralize the improvements in the Slicer CLI code so all extensions can be cleaner. 👍 |
@ihnorton we discussed this on the radiomics call and like the direction. Do you know what the circleci issue is? What will it take to support for the build vs install versions of Slicer's python? |
Note that on Windows, python-real does not run (cannot find DLLs) but you have to use SlicerPython.exe launcher. While you are at it, it would be great to change SlicerPython.exe executable name to PythonSlicer.exe, as PyCharm (and maybe other software?) requires Python interpreter name to start with "Python". Is this going to be configurable to use various Python interpreters? We usually want to use Slicer's Python but it could be nice to be able to use others. |
381f3f0
to
e3879ac
Compare
Eliminated need to wrapper stub, which is blocked by macOS SIP policy and has some other drawbacks. Ref: Slicer/Slicer#894
Thanks!
Still building against Qt 4.7, which appears not to have
Well, to keep this quick-and-simple for now, I just updated the PR to
If we want to do something more involved (as per below -- which I think is a good idea), then I'd like to work out a plan on a call before implementing.
I haven't tried this change on Windows, but I believe that issue would be mitigated in this particular context, because python-real would be invoked with Slicer's
Agreed. I'll keep that in mind to look at in the future (probably it's only a few lines, but making that kind of change usually ends up with CMake and packaging issues that I can't debug right now).
I thought about that a little bit but don't have a good suggestion yet. It would be very nice to support multiple python (e.g. conda) and even non-python interpreters via CLI, but that would require some deeper refactoring of the CLI initialization. The loader first needs to decide at startup whether to consider a given file as a valid executable, what kind of CLI factory to use, before even approaching the interpreter question. The current logic for "ExecutableCLI" is: |
within C++ CLI, we support restoring the environment excluding Slicer one, this allows us to use script expected to work with anaconda, etc .... We add the following at the top of the main function of the c++ CLI
Ideally, we should extend the SlicerExecutionModel to allow ignoring the parent environment. |
@jcfr to follow up on your comment, the goal of this PR was to allow running python code in the SlicerPython interpreter without any intermediate step (currently: If we add that feature, then there's also the question raised by Andras about whether to support arbitrary interpreters. Which could be conda-python, lua, whatever -- and of course there are work-arounds to do that right now, with some downside/complexity. Technically we could, I'm just not sure how to configure it. |
What would folks think about changing the CLI identification logic from:
Then any script starting with |
Shouldn't we just check for .py extension as well? Would you remove the .py extension from Python files? |
Currently the module loader seems to treat all .py files in all module directories as potential scripted modules. We could change that of course.
Maybe yes, for this type of "CLI direct exec" Python script, but depends on what we do for the above issue. |
I see. The problem is that .py files are always treated as Python scripted modules and there is no way to indicate that they are CLI modules. Giving a hint in the file content could be a good idea. We already have dual-mode Python files, which can act as a module or as a slicelet, for example LabelStatistics.py. We should implement a solution that provides simple and consistent solution for all 3 cases:
Using #! to indicate that it is a CLI seems to be a good idea, as it would mimic what a unix-like OS would do, and if the file extension is kept as .py then Slicer application could emulate the same behavior on Windows. Dual-mode (A and B) modules could be simplified by splitting to 2 Python files - one would remain a scripted module only, and a second could be just a script file. Probably we would need to define a special !# code for type B script. Could that be done in a standard way so that on a unix-like platform the script would actually executed by launching Slicer application like this?
Could there be a variant to run with GUI?
|
Another way to deal with this is to have |
By using a |
@lassoan yes, just wanted to mention it, since you mentioned splitting to separate modules from script files to handle dual-mode (A and B). |
Assuming we make
So I think the simplest solution is to leave the decision about what to do in mode B (with/without
Regarding:
If the above sketch is not sufficient, can you explain a little bit the expected behavior difference between these two invocations? (note that |
Oh, for this point:
The convention is that the script has a GUI if a class called |
There are python scripted modules that don't have widget class. So, if the widget class is missing, it may still be a (non-CLI) scripted module. |
Non-CLI scripted module handling should be be unaffected unless they happen to have |
Base/QTCore/qSlicerUtils.cxx
Outdated
@@ -51,6 +51,11 @@ bool qSlicerUtils::isExecutableName(const QString& name) | |||
//------------------------------------------------------------------------------ | |||
bool qSlicerUtils::isCLIExecutable(const QString& filePath) | |||
{ | |||
if (filePath.endsWith(".pycli")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it should be Qt::CaseInsensitive comparison (at least on Windows)
Looks good overall. Even if some issues come up we can always tune it further. |
Great. I will also review later tonight.
…On Mar 7, 2018 9:13 PM, "Andras Lasso" ***@***.***> wrote:
Looks good overall. Even if some issues come up we can always tune it
further.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<Slicer/Slicer#894 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AANXoygIkhhLfq9OADSd1sYe3cOa3JQRks5tcJPdgaJpZM4SMlmr>
.
|
I will update to reflect the discussion. |
e3879ac
to
c58c66f
Compare
Eliminates need for wrapper stub, which is blocked by macOS SIP policy and has some other drawbacks. Ref: - https://discourse.slicer.org/t/tutorial-for-using-pyradiomics-no-module-named-collections/2111 - Slicer/Slicer#894
c58c66f
to
37da5c7
Compare
Eliminates need for wrapper stub, which is blocked by macOS SIP policy and has some other drawbacks. Ref: - https://discourse.slicer.org/t/tutorial-for-using-pyradiomics-no-module-named-collections/2111 - Slicer/Slicer#894
37da5c7
to
3f761e2
Compare
Eliminates need for wrapper stub, which is blocked by macOS SIP policy and has some other drawbacks. Ref: - https://discourse.slicer.org/t/tutorial-for-using-pyradiomics-no-module-named-collections/2111 - Slicer/Slicer#894
Updated and added test for this functionality. |
I'd like to integrate this week. Please let me know any comments/objections if anyone has a chance to review (or tell me if I should wait). Thanks! |
I think we've discussed this enough, it should be integrated and we can improve later if needed. It should not delay the integration, but if you have time, it would be great if you could add a PyCLI module template to the ExtensionWizard. Thanks a lot for working on this, it will be great to have this feature! |
@ihnorton do you still plan to integrate, or there are some extra items that need to be resolved first? |
Base/QTCore/qSlicerUtils.cxx
Outdated
@@ -51,6 +51,18 @@ bool qSlicerUtils::isExecutableName(const QString& name) | |||
//------------------------------------------------------------------------------ | |||
bool qSlicerUtils::isCLIExecutable(const QString& filePath) | |||
{ | |||
// check if .py file starts with `#!` magic | |||
// note: uses QTextStream to avoid issues with BOM. | |||
QFile scriptFile(filePath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest to move this code into a function that can be reused from Base/QTGUI/qSlicerScriptedLoadableModuleFactory.cxx
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be a function named isCLIScriptedExecutable(cons QString& filePath)
(scriptStream.readLine(2).startsWith("#!")) ) | ||
{ | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then, the code above would be simplified to:
if (qSlicerUtils::isCLIScriptedExecutable(file.filePath()))
{
return false;
}
I also suggest to re-base and force push the topic, then CircleCI will be able to build the topic. |
Thanks. I should be able to address comments and template tomorrow, then
merge.
|
Eliminates need for wrapper stub, which is blocked by macOS SIP policy and has some other drawbacks. Ref: - https://discourse.slicer.org/t/tutorial-for-using-pyradiomics-no-module-named-collections/2111 - Slicer/Slicer#894
3f761e2
to
dd39fd5
Compare
Eliminates need for wrapper stub, which is blocked by macOS SIP policy and has some other drawbacks. Ref: - https://discourse.slicer.org/t/tutorial-for-using-pyradiomics-no-module-named-collections/2111 - Slicer/Slicer#894 git-svn-id: http://svn.slicer.org/Slicer4/trunk@27143 3bd1e089-480b-0410-8dfb-8563597acbee
Committed in r27143-r27145 with @jcfr comments addressed. I will make a separate PR for the template. |
@@ -20,6 +20,7 @@ | |||
|
|||
// Qt includes | |||
#include <QProcess> | |||
#include <QStandardPaths> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was added in Qt5, so it breaks a Qt4 build. @jcfr had some use cases where he wanted to keep Qt4 working at least for a while, so for now I'll just stub out this functionality unless building with Qt5 (his other projects won't need it). Hopefully soon we can make a pass to remove all Qt4 support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Committed as r27146
Unfortunately, this did not resolve the issue mentioned in the opening comment for this PR: AIM-Harvard/SlicerRadiomics#43:
|
@fedorov it doesn't look like there was any change made to use this feature yet? You would need to install this script as a (Python) CLI directly, instead of using the sh/bat wrappers. IIRC SlicerRadiomics has a macro to do that already (used for the wrappers right now). I did that test manually in local SlicerRadiomics build tree, while developing this branch, and it worked. Let me know if it doesn't and I'll check again. |
Ah, I missed it completely. Thanks for the pointers Isaiah! |
Remove use .bat and .sh wrapper script, and instead define the CLI entry point as a python script. This makes use of the new Slicer functionality described in Slicer/Slicer#894 and implemented in r27143-r27145. This fixes #43. Additionally, check if a `--label <label>` argument is present, and if so, update it to the new-style: `--setting=label:<label>`. Parameter `--label` is deprecated by PR AIM-Harvard/pyradiomics#347.
I confirm this seems to work in a local build. This feature was now integrated into SlicerRadiomics in AIM-Harvard/SlicerRadiomics#46 by @JoostJM. Need to confirm that it works in the nightly. Kudos to @ihnorton! |
This is a quick hack demonstrating a potential solution to this issue and related problems (e.g. running
multiprocessing
).tl;dr, this detects a
.pycli
file in a CLI directory (cli-modules
), and then configures the CLI node to run the script via Slicer's python interpreter. As an example, for SlicerRadiomicsCLI, this gives the following command line:The command is exec'd via the existing CLI mechanism, runs out-of-process and async as any other CLI would be, but it inherits the full Slicer environment without requiring any launcher/cmake/etc. steps. Running directly also avoids OS security policy issues caused by shelling-out to protected interpreters.
Note that this is different from the current scripted CLI module path which runs in-process and blocks on the interpreter for the duration of the call (but should have no effect on that code path).
This PR does not handlepython-real
in a factory-packaged Slicer, and other issues, but I wanted to put it up for feedback before spending time on it.cc @fedorov @lassoan @pieper @ljod