-
Notifications
You must be signed in to change notification settings - Fork 2
BQ_module_generator Design Choices
BQ_module_generator has 3 main components:
- bqmod (A command-line interface): Generates the module xml.
- PythonScriptaWrapper: Parses module xml, establishes communication with Bisque, runs module.
- BQ_run_module.py: User-defined file called by PythonScriptWrapper that reads data from the module container, pre-processes it, runs inference, and returns output paths to PythonScriptWrapper.
BQ_module_generator is structured such that users don't need to edit the PythonScriptWrapper or xml files to create modules. These files are the most tedious and time-consuming part of the process. The PythonScriptWrapper in this repo has been written such that it can establish communication between Bisque and modules that follow certain naming conventions and guidelines. This allows users to build modules by only dealing with their own code rather than learning to use the Bisque API. Furthermore, users can utilize a command-line interface (CLI) to generate a standardized xml file of their module that PythonScriptWrapper can parse and establish communication with Bisque. This CLI sets the configuration of the module with easy-to-use commands in the terminal. Finally, the BQ_run_module.py is the only code that the user needs to write in order to create their module. This file will have a run_module function which will be called by PythonScriptWrapper at runtime. This function will have to follow some specific naming conventions and structure in order to correctly parse the arguments being passed by PythonScriptWrapper as well as return the appropriate data.
Bqmod is the command-line interface that helps users set the configuration settings needed to generate the module xml file. This file is read by both Bisque and PythonScriptWrapper. Bqmod is implemented in bqmodule.py with the help of the click library. To get familiar with this library, read the click library documentation.
The main steps for bqmod are:
- Create bqconfig file with 'bqmod init' in {ModuleName} folder
- Set module name, author, and short description with 'bqmod set'
- Set input resource types and names with 'bqmod inputs'
- Set output types and names with 'bqmod outputs'
- Review module configurations with 'bqmod summary'
- Check BQ_run_module dictionary keys match bqmowd input/output names with 'bqmod check_config'
- Create module files with 'bqmod create_module'
- Generate help.html from help.md with 'bqmod gen_help_html'
There are a number of functions in bqmodule.py that are used for dealing with each of the bqmod commands. Most are very well documented in the code and you can read more about them there, some that might need a little more attention are:
-
bqmod: This is what click refers to as the group command since all commands under it will need to be prefixed by
bqmod
command. Thus, anytime we call a command, the code in the bqmod function will run first. We can control the behavior of this bqmod function with respect to which subcommand is called by using the ctx.invoked_subcommand attribute,if ctx.invoked_subcommand == 'init': pass
. By default, this function will always load in the bqconfig.json file present in the current directory into a dictionary saved as the context objectctx.obj
. Only when the invoked subcommand isinit
, will this not happen since it probably means there is no bqconfig.json created yet or the user means to overwrite it. As you may read in the click documentation, any command you wish to be part of this group will use the decorator@bqmod.command("name_of_command")
. Thectx.obj
mentioned earlier is an object passed to all commands of the bqmod group that have the decorator@click.pass_context
. We use it in bqmod to pass the dictionary stored in bqconfig.json in order to make changes, append to it, and eventually to created the module xml. - download_files: This function is used to pull the necessary files into the module folder, if the URL for any file changes, it must be reflected here.
- check_config_main: As explained in the README of the project, the dictionary keys used in BQ_run_module must match the input and output names set in the CLI. In order to avoid bugs, this function checks whether there are any inconsistencies between these.
PythonScriptWrapper reads and parses the module xml to identify the number, types, and names of the inputs and outputs in a module. This is how it is able to establish communication with Bisque for modules with a wide variety of input/output types and quantities. PythonScriptWrapper has x main components.
-
main: parses arguments, ensures all necessary credentials are passed, initializes Bisque session, calls run function, and calls upload_results function.
-
setup: updates mex message, calls mex_parameter_parser, and instantiates self.output_resources list
-
self.mex_parameter_parser: Parses inputs from the mex XML which is different from the module XML, and adds the input uris to the options attribute so they can be loaded by
fetch_input_resources
.
-
self.mex_parameter_parser: Parses inputs from the mex XML which is different from the module XML, and adds the input uris to the options attribute so they can be loaded by
-
run: Sets the container's
inputs_dir_path
andoutputs_dir_path
. By default, they are set to the current working directory/module
. If changed, must make sure that thefetch_input_resources
function is pulling inputs to the correct folder. Then has a series of try catches that callfetch_input_resources
,run_module
, andupload_results
.-
fetch_input_resources: Instantiates
input_path_dict dictionary
which will contain the paths within the container to the inputs. It then finds the tag within the module xml with the nameinputs
and iterates through its child tags that have the typeresource
. It then finds each input name from the module xml and usesbq.load(getattr(self.options, input_name))
to create abqapi.bqclass.BQResource
object which has the following attributes:name
,uri
,ts
, andresource_unique
. We then use this object to setinput_path_dict[input_name] = os.path.join(inputs_dir_path, resource_obj.name)
andfetch_blob_output = fetch_blob(bq, resource_obj.uri, dest=input_path_dict[input_name])
. Important to note thatinput_name
refers to the name set withbqmod inputs -{i,f,t} -n {input_name}
whileresource_obj.name
refers to the name of the file uploaded to bisque for inference, eg. 'whale.jpeg', 'yolov5s.pt', 'input_matrix_file.npy'. The functionfetch_blob(bq, resource_obj.uri, dest=input_path_dict[input_name])
pulls the resource with URIresource_obj.uri
from Bisque and puts it inside the container with the path determined byinput_path_dict[input_name]
. Finally, it returns theinput_path_dict
which has the container paths to each input. -
run_module: This will be the user-defined function imported from BQ_run_module.py. It will take the
input_path_dict
created infetch_input_resources
and theoutput_folder_path
defined in__run__
. It will return a dictionaryself.output_data_path_dict
which will contain the paths to each output within the container indexed by the output name set withbqmod inputs -{i,f,t} -n {output_name}
. -
upload_results: Instantiates
output_resources
which is a list of xml tag strings for each output defined in the module xml.
-
fetch_input_resources: Instantiates
- teardown: takes the output xml tags set in run, creates an output xml tag that contains all output tags with their respective uri, and passes this to finish_session which updates the mex with this output tag.
-
setup: updates mex message, calls mex_parameter_parser, and instantiates self.output_resources list