Skip to content
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

Counting T1 transitions #239

Open
glyg opened this issue May 21, 2021 · 21 comments
Open

Counting T1 transitions #239

glyg opened this issue May 21, 2021 · 21 comments

Comments

@glyg
Copy link
Member

glyg commented May 21, 2021

Here is an email from Chi Xu, who works with @gcourcou

This is Chi, I am working with George Courcoubetis on tissue research. Currently we want to visualize T1 transitions in a growing tissue. Specifically, we want to get the number of T1 transitions and the place(which cells) where they happen in the tissue when we use find energy min function with T1 transitions is on. However I found it's not easy to do so. I keep track of the number of sides for each cell, if it's changing I assume T1 transitions happen. But it could be different(like cell divisions, or T1 transitions happen twice on a cell which keeps the number of sides the same for that cell). So I am sending this email to ask whether there is an explicit way to show the number of T1 transitions and the place where they happen during finding energy min process.

For now there is no explicit way to do that.

A solution would be to be able to pass a on_topo_change callback to the QSSolver class to be called here. I should have time to open a PR for that next week. It can be done by subclasseing QSSolver:

class QSSolverT(QSSolver):

    def __init__(self, on_topo_change, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.on_topo_change = on_topo_change

    def _minimize(self, eptm, geom, model, **kwargs):
        for i in count():
            if i == MAX_ITER:
                return self.res
            pos0 = eptm.vert_df.loc[
                eptm.vert_df.is_active.astype(bool), eptm.coords
            ].values.flatten()
            try:
                self.res = optimize.minimize(
                    self._opt_energy,
                    pos0,
                    args=(eptm, geom, model),
                    jac=self._opt_grad,
                    **kwargs
                )
                return self.res
            except TopologyChangeError:
                log.info("TopologyChange")
                self.on_topo_change(eptm)
                self.num_restarts = i + 1

A longer term solution, also more generic & interesting is to have a better History object that could keep track of the objects throughout rearrangements (e.g as a 4D graph). This could go with a redefinition of the I/O.

@xcricardo
Copy link

Thank you Guillaume! What is on_topo_change? How can we get the number of T1 transisions by using it? Is the code above enough to obtain the number of T1 transitions?

@glyg
Copy link
Member Author

glyg commented May 27, 2021

Hi, on_topo_change should be a function taking eptm as it's only parameter. For example:

def count_t1(eptm):
    if "num_type1" in eptm.settings:
        eptm.settings["num_type1"] += 1
    else:
        eptm.settings["num_type1"] = 1

solver = QSSolverT(on_topo_change=count_t1)
. . .

I realize now that this would count more changes than just T1s...

Maybe a better way to do it would be through the "opposite_face" column in sheet.face_df. The get_opposite_face method would allow you to see if there are neighbor changes.

@gcourcou
Copy link
Contributor

gcourcou commented Jun 9, 2021

Regarding the T1 transitions: We wrote a code that outputs T1 transitions. We did not identify any T1 transitions during our growing tissues. We use the auto T1 transition option in the energy minimization step. Have you tested that auto_t1 actually works? Also we noticed a magic number to identify short edges eligible for T1 transitions 1e-6. Why was that choice made? Can we vary it and see its effect? Thank you so much, we currently submitted to PLOS Comp bio and we have positive reviews but need to address many questions. It is a bit worrisome that we do not see any T1s, it would seem like a great way for the tissue to relax, which could lead to the prevention of avalanches, which is the central subject of our manuscript.

@xcricardo
Copy link

xcricardo commented Jun 9, 2021

This is our code for testing the t1 transitions between frames. It failed when the indexes got shuffled.

for jump_index in jump_indexes:
    print(jump_index)
    dsets1 = hdf5.load_datasets(name + str(jump_index - 1) + ".hf5")
    specs1 = config.geometry.planar_sheet()
    before = Sheet("periodic", dsets1, specs1)

    dsets2 = hdf5.load_datasets(name + str(jump_index) + ".hf5")
    specs2 = config.geometry.planar_sheet()
    after = Sheet("periodic", dsets2, specs2)

    # t1 transitions
    # find neighborlist first
    beforeneighbor = dict()
    afterneighbor = dict()
    for index, row in before.edge_df.iterrows():
        key = row["face"]
        val = row["opposite"]
        if val != -1:
            if key in beforeneighbor:
                beforeneighbor[key].add(before.edge_df.at[val, "face"])
            else:
                beforeneighbor[key] = set()
                beforeneighbor[key].add(before.edge_df.at[val, "face"])

    for index, row in after.edge_df.iterrows():
        key = row["face"]
        val = row["opposite"]
        if val != -1:
            if key in afterneighbor:
                afterneighbor[key].add(after.edge_df.at[val, "face"])
            else:
                afterneighbor[key] = set()
                afterneighbor[key].add(after.edge_df.at[val, "face"])

    # compare
    #    difface = []
    #    for index, row in before.face_df.iterrows():
    #        if row['num_sides'] != after.face_df.at[index, 'num_sides']:
    #            difface.append(index)
    #    print("difface is:")
    #    print(difface)
    t1_set = set()
    for key in afterneighbor:
        if key in beforeneighbor:
            for face in afterneighbor[key]:
                if face not in beforeneighbor[key]:
                    #                    print("new neighbor:")
                    #                    print(face)
                    if face in beforeneighbor:
                        print("t1:")
                        print(face)
                        t1_set.add(key)`

@glyg
Copy link
Member Author

glyg commented Jun 10, 2021

@gcourcou :

Regarding the T1 transitions: We wrote a code that outputs T1 transitions. We did not identify any T1 transitions during our growing tissues. We use the auto T1 transition option in the energy minimization step. Have you tested that auto_t1 actually works?

Reading the code, it should work but I haven't tested it in some time. I'll draft a test today to ensure T1 do happen. I checked, and they do, see link to notebook at the end of this answer

Also we noticed a magic number to identify short edges eligible for T1 transitions 1e-6.

Yes you can see it here

Why was that choice made?

This is a default value, sufficiently small to avoid problems in case of small tissue. Other than that it is arbitrary but

Can we vary it and see its effect?

Absolutely, it is the "threshold_length" parameter, its actual value is read from the sheet.settings dictionnary (as you can see in the code line linked above). So setting:

sheet.settings['threshold_length'] = 1e-2

will change the threshold fot T1 transitions.

, we currently submitted to PLOS Comp bio and we have positive reviews

Awesome, congrats!

It is a bit worrisome that we do not see any T1s, it would seem like a great way for the tissue to relax, which could lead to the prevention of avalanches, which is the central subject of our manuscript.

Hopefully changing the threshold will do the tick, I'll try to have a test ready quickly


@xcricardo

The spirit is good, should not take much to fix. As you noticed, re-indexing scrambles the indices, so in order to have consistent data, you need to add an "id" column to the objects you want to track:

sheet.face_df['id'] = sheet.face_df.index

(This column is later updated if present when a cell division occurs)

If you don't have division or apoptosis though, face indices should not change (I don't know if it's your case).

I cooked up a function that returns the set of face pairs that changed between to transitions:
Look at this notebook

Hope all of this helps!

@gcourcou
Copy link
Contributor

Thank you Guillaume yes, this helps!! We do have mitosis 100% We will look into your notebook and see if we can use parts of it.

@glyg
Copy link
Member Author

glyg commented Jun 11, 2021

So yes if you have mitoses, you need to setup the "id" at the bigining of the simulation. This actually should be the default for cells (faces in 2D) I think, it never makes sens to not want that info!

I think with the linked code, you can later on make sense of wether a T1 happened or a division

@gcourcou
Copy link
Contributor

Thanks!! I do want to avoid re-running the simulations but it should be easy to do!

@gcourcou
Copy link
Contributor

@glyg I used

 sheet.face_df['id'] = sheet.face_df.index

and I am getting duplicate IDs in my sheet.face_df['id'] column. E.g. there are multiple cells with an id of 2 e.t.c. I use daughter = cell_division(sheet, index, geom, angle=angle_div) for the division. (from tyssue.topology.sheet_topology import cell_division)

Let me know how I can fix that. Thanks again for all your help!!

@glyg
Copy link
Member Author

glyg commented Jun 12, 2021

Ha I thought the id column was updated in cell_division, but it must not be the case, sorry. So you have to manually do:

daughter = cell_division(sheet, index, geom, angle=angle_div)
sheet.face_df.loc[daughter, "id"] = sheet.face_df.index.max() + 1

@glyg
Copy link
Member Author

glyg commented Jun 12, 2021

Also for now as a bug fix (in case you don't want to re-run your simulations!) , daughter cells will have the same id as their mother but I think always a higher index (re-indexing only shifts indices to the right when a cell is missing for example), so you should be able to de-uniquify them.

@gcourcou
Copy link
Contributor

Thanks @glyg , really appreciate your prompt responses! Lastly, could you elaborate on how auto_t1 works in relation to energy and IH transition (which i do not understand). Is it basically saying if an edge is less than the threshold, then try to do a T1, if the T1 leads to lower energy, then keep it?

@glyg
Copy link
Member Author

glyg commented Jun 13, 2021

Hi, no problem,

No, the T1 just happens if the length is lower than the threshold, there is no energy evaluation. The gradient descent is simply restarted. The transition would be reverted only if the length of the new junction stays lower at the next gradient descent step.

This is actually a problem with T1 transitions described like that, the possibility of oscillation. The Finengan et al. paper has a better algorithm (which is implemented in tyssue, but only integrated in the EulerSolver, it is less straight-forward to do so without a time scale in a quasistatic model): T1 is not a single step event but rather first you form a 4-way vertex, that gets resolved stochastically by one of the 4 junctions detaching.

You can look at this notebook for more details

@gcourcou
Copy link
Contributor

gcourcou commented Jun 14, 2021

Ha I thought the id column was updated in cell_division, but it must not be the case, sorry. So you have to manually do:

daughter = cell_division(sheet, index, geom, angle=angle_div)
sheet.face_df.loc[daughter, "id"] = sheet.face_df.index.max() + 1

We are still getting repeating indexes in the output. Any guess why ?
Maybe use sheet.face_df.id.max() instead of .index.max()?

@glyg
Copy link
Member Author

glyg commented Jun 15, 2021

Maybe use sheet.face_df.id.max() instead of .index.max()?

That might be a good idea yes :/

@gcourcou
Copy link
Contributor

Hi @glyg could you explain how auto_t3 works in the code? (should be t2 not t3! according to literature) What is the parameter that sets if a cell will be removed?

@glyg
Copy link
Member Author

glyg commented Jun 26, 2021

Hi @gcourcou sure I can explain. relevant code is
here

We follow Okuda et al (the Recombinant Network Reconnection paper) rule for a type 3 transition i.e the elimination of a triangular cell from the apical surface -- maybe it's type 2, I haven't revised the literature in a while, and I might have been wrong when I wrote that!
The rule states that a triangular face is eliminated if it's longest edge is shorter than the threshold length, the same setting as for type 1.

@glyg
Copy link
Member Author

glyg commented Jun 26, 2021

and yes according to that paper it is a type 2 transition. We should duplicate the function with a deprecation waring for the old name - implementing the true T3 could be interesting too! Thanks for pointing this out

@gcourcou
Copy link
Contributor

gcourcou commented Jun 28, 2021

Thanks @glyg !! I appreciate your response. It is slightly different from the area threshold used in other studies but seems equally valid to me. Glad i could point out something to improve the library. I also would recommend setting the T1 transition length to 0.01 but that is up to you see this link .

@glyg
Copy link
Member Author

glyg commented Jun 29, 2021

Thanks for the ref, I guess we can trust A. Fletcher ^^'

If you don't mind, could you change the default for threshold_length in your opened PR?

@gcourcou
Copy link
Contributor

gcourcou commented Jun 29, 2021

Sure, done!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants