Uploading .stix file into a DstabilityModel (Geolib)

geolib@deltares.nl

Hi all,

I am also bumping into a similar issue. Has one of you found a solution yet?

I feel like I almost got it but there is an error I didn’t understand.

In my controller I have the following code:

 def see_how_stix_works_here_in_viktor(self, params, **kwargs):
        stix_file  = params.stix_file_input #FileResource 
        # https://docs.viktor.ai/docs/guides/integrations/geolib/
        temp_file = NamedTemporaryFile(suffix='.stix', delete=False,
                                           mode='wb')
        temp_file.write(stix_file.file.getvalue_binary())
        temp_file.close()
        path = Path(temp_file.name)  
        parsed_file = DStabilityModel().parse(path)
        os.remove(temp_file.name)
        print(parsed_file) 
        return parsed_file 

This resulted in parsed_file = None

However, if I change

  parsed_file = DStabilityModel().parse(path) 

into

  parsed_file = DStabilityModel()
  parsed_file.parse(path) 
  print(parsed_file)

I can see the DStability-data, something like
filename=WindowsPath(‘…/AppData/Local/Temp/tmp2v2_z79z.stix’) datastructure=DStabilityStructure(waternets=[Waternet(Id=‘161’, ContentVersion=‘1’, PhreaticLineId=‘162’, HeadLines=[PersistableHeadLine(Id=‘162’, Label=‘PL1’, Notes=‘’, Points=[PersistablePoint(X=-95.0, Z=1.455), …

To me, this looks quite good. However, this outputis followed by an error, which is this: (this is the part I don’t understand anymore)

2022-06-30 11:56:46.640 ERROR   : Exception is raised
Traceback (most recent call last):
  File "viktor_connector\connector.pyx", line 400, in connector.Job.execute
  File "viktor\core.pyx", line 1799, in viktor.core._handle_job
  File "viktor\core.pyx", line 1783, in viktor.core._handle_job.non_view_result
AttributeError: 'DStabilityModel' object has no attribute '_serialize 

What would be a solution to solve this error? Your help is very much appreciated :slight_smile: btw, I am using GeoLib 0.1.6 and DStability 2020.03

Hi Yida,

Do you invoke see_how_stix_works_here_in_viktor using a button? In this case the method should return the corresponding Result type, for example a DownloadButton should return a DownloadResult (see Managing files - Downloading files | VIKTOR Documentation).

Hi Kevin,

Thanks a lot. Indeed, I made a button. I overlooked that part of returning the corresponding button-result. Now I have a new question: is there any best practice how to store the geolib-models?

In my parametrization I have the following fields

class MyParametrization(Parametrization):
   stix_files = FileField('stix file')
   btn_2 = SetParamsButton("Stix test", "see_how_stix_works_here_in_viktor", longpoll=True)
   my_dataset = HiddenField('no UI', name="my_dataset")

In the controller method, I’d like to store the DStabilitymodel()-object including all the class variables. I was trying this but since parsed_file is not a dictionary but an object it gives an error.

def see_how_stix_works_here_in_viktor(self, params, **kwargs):
  ...
  return SetParamsResult({'my_dataset': parsed_file})
2022-06-30 13:48:41.923 ERROR   : Result can not be serialized to JSON: Object of type DStabilityModel is not JSON serializable

So my question is, is there any best practice how to store a DStability()-object including all class variables?

Hi Yida, why would you like to store the object? Does parsing the stix take a lot of time?

Hi Kevin,

Thanks for your question :pray:

Here is a bit of background information. I am trying to build an app and I have some existing code stored on my local computer. The objective of that code is to make a summary/appendix based on many stix-files (say >20) (and maybe also some other types of input data such as Excels). Furthermore, one key variable in my object is a list of DStability()-objects.

First, for the app it would be nice upload the stix files and store them. Then let the user select using buttons/boolean switches which type of output (tables/figures/document) they want, including the level of detail. Lastly, execute the tool using a button and download the result.

When the user hits the ‘execute’ button, I don’t want the data to be parsed every time they make a small change in the button/switches, hence my question. Also, storing a (list of) DStability()object(s) would make a smooth connection to the current code.

Is there a way to store the object? Or would you recommend another way? I thought an alternative might be parsing, executing the tool, and downloading, all in one go.

Thanks for the explanation.

It is not possible to store a DStability object (or any object) since this is not json serializable (e.g. string, int, bytes, etc.). We do offer the following options for storage:

However, these options in turn require the data to be json serializable which means you will need to serialize the DStability object, and subsequently de-serialize (parse) when retrieving. So you will still be left with the parsing step.

I can’t really think of another way, but I am wondering how much time does the parsing take?

Hi Kevin, thanks for your reply.

Parsing 15 stix files takes little more than 4 seconds :slight_smile: to be precise for hitting the parsing 6 times:
— 4.299708843231201 seconds —
— 4.3733580112457275 seconds —
— 4.385324239730835 seconds —
— 4.614434003829956 seconds —
— 4.221465587615967 seconds —
— 4.275190353393555 seconds —

Hi all,

The original problem posted by Jordi (@jdeleau ) is that the parsed_file = DSettlementModel().parse(file_path) is resulting in parsed_file being None. This is actually expected!

What the parse() function is doing is parsing the provided file into the DSettlementModel.
As @yida.tao already discovered in her post.

So the workflow should be as follows:

dsettlement_model= DSettlementModel() # Empty model
dsettlement_model.parse(Input_or_output_file) # parse input or output file into the model

After this, the data is in the model can be used in further calculations.

@bvanderhulst
The snippet in the documentation is kind of misleading:

temp_file = NamedTemporaryFile(suffix='.sld', delete=False, mode='wb')  # create a temporary file with correct suffix; don't delete on close(); remove mode 'b' in case of StringIO/str
temp_file.write(my_bytesio.getvalue())  # write in-memory content (bytes in this case) to file
temp_file.close()  # close (does not delete) the file to ensure the data is actually written to file (instead of kept in buffer)
path = Path(temp_file.name)  # name returns the path in `str`, GEOLIB requires (in most cases) a Path object
parsed_file = DSettlementModel().parse(path)  # obtain the parsed results
os.remove(temp_file.name)  # remove the temporary file to avoid cluttering of files

The second to last line should be replaced by:

dsettlement_model = DSettlementModel() # create empty model
dsettlement_model.parse(path)  # parse file into DSettlementModel() model
1 Like

@mweehuizen. I have implemented you proposed solution as well as a solution using a .sld file in the repository. However I get an error as shown in the image below. Are you familiar with this issue?

Both options I tried are shown below:

        # Viktor file
        d_settlement_model = ff.d_settlement_model()
        results = run_d_settlement_analysis_from_model(d_settlement_model)  # returns as str in results

        # d_settlement_output_path = MODEL_FILE_PATH / "output_dsettlement.sld"  # Path to be used directly in DSettlementModel.parse()
        # 
        # with open(d_settlement_output_path, "w") as f:
        #     for line in results.split(sep="\n"):
        #         f.write(line)
        #     f.close()

        temp_file = NamedTemporaryFile(
            suffix=".sld", delete=False, mode="w"
        )  # create a temporary file with correct suffix; don't delete on close(); remove mode 'b' in case of StringIO/str
        temp_file.write(results)  # write in-memory content (bytes in this case) to file
        temp_file.close()  # close (does not delete) the file to ensure the data is actually written to file (instead of kept in buffer)
        path = Path(temp_file.name)  # name returns the path in `str`, GEOLIB requires (in most cases) a Path object
        output_model = DSettlementParser()
        output_model.parse(path)  # parse file into DSettlementModel() model
        os.remove(temp_file.name)  # remove the temporary file to avoid cluttering of files

        result_table = output_model.table_calculated_settlements()

        return WebResult(html=StringIO(result_table.to_html()))

The parser works locally using the GeoLib, it doesn’t work when implementing it within the Viktor environment. Any idea?

Hi Johan,

I am not sure what is going wrong. What is returned by the run_d_settlement_analysis_from_model function?
Can you print results and post it here?

Hi Maarten,
The output is a string shown in the file below.

output_dsettlement.sld (294.3 KB)

If we run the GeoLib locally it works fine with the same input. However through the Viktor workers loading it into the GeoLib Parser it raises this error.

Hi,

I actually wanted to know what kind of object type results was (bytes, BytesIO, String).

But i think i spotted the problem:

Can you change this line:

output_model = DSettlementParser()

to:

output_model = DSettlementModel()

Results is a string (contents of the .sld file). The output_model is a direct inheritance of the DSettlementModel of the GeoLib. only certain methods have been added.
Replacing this results in the same error.

I am using geolib: d-geolib==0.1.6.

Not sure what other settings could be different to mine. As I think the code related to transferring the str to the Geolib Model is simular…

@mweehuizen I think it could have something to do with using Python 3.10. Which had some mayor changes in Typing. I don’t use any other features of 3.7+, so I thought this wouldn’t give any problem.s Installing 3.7 now to check if this was the case

Hi Johan,

I did some in depth investigation and it appears that Geolib (both 0.1.6 and 0.1.7) is not compatible with python 3.10.
I have ran the above code with python 3.7 and 3.10 and the error occurs only on python 3.10.

But i am confused by the fact that it works locally with you.
What version of python are you running ‘locally’ and with what version is the viktor-cli configured?

The first one can be checked by running python from cmd:

The second can be checked in c:\Users<your_username>.viktor\configuration

You can change you local python version used by viktor following these steps:

  1. Install different python version (bijv. 3.7.9)
  2. Change python_path using viktor-cli.exe configure and change python path to the correct python folder
  3. remove the \venv\ folder from the app folder
  4. run viktor-cli.exe install

few minutes ahead of me :wink:

1 Like

Hi all,

Just received a link that there is a new Geolib release - v.0.1.8:
Release v0.1.8 · Deltares/GEOLib (github.com)

One of the features is that support for Python 3.10 is added. Good luck with programming @Johan_Tuls !

2 Likes

Hi @yida.tao ,

Thanks for sharing.
Pip still only contains v0.1.7. Even though v0.1.8 release is present on github.com.

Hopefully Deltares starts putting effort to make Geolib up to date with all D-Series / Python versions. Personally I think we should sign a petition with all contractors, engineering companies and platforms to request this.

I will suggest this to be set on the Agenda of the next DEC meeting also.

Hi Johan,

I already started a ‘discussion’ on the github and got a response:

Hi @Mbweehuizen, yes it will be! The person who is authorized to do this is currently on holiday, we will upload it to PyPi when he returns and make sure we can do it faster next time

So v0.1.8 will be on PyPi soon.

Furthermore, I agree with your second point