Icon representing a recipe

Recipe: Power Shift 1.0.1 - Brow42

created by brow42

Profile


Name
Power Shift 1.0.1 - Brow42
ID
44189
Shared with
Public
Parent
None
Children
None
Created on
September 24, 2012 at 06:09 AM UTC
Updated on
September 24, 2012 at 06:09 AM UTC
Description

Thread a group of segments like you can in the alignment tool. Actual shifting is done with the tweak tool.

Best for


Code


--[[ * Power Shift 1.0.1 * Original Author: Brow42 * Version 1.0 Sep. 19 2012 * This script will help you shift a long part of the * protein using the tweak tool. First, use either * selection or freeze to mark all the segments you want * to shift. The script will turn all of those into * sheets. It will also add at least 2 loops to both end * which are required by tweak. It will save the original * structure in the notes for later. * After running the script the first time, you can now * use the tweak tool on the sheets to shift them. After * you have shifted, run the script again. The script should * automatically determine how far you shift. It will then * apply the original secondary structure to their original * positions in space. * Version 1.0.1 Sep 23, 2012 brow42 * Adjusted selection so the tweak tool shows up in selection interface --]] title = "Power Shift 1.0" NoteTag = "Shift" -- our data starts with this string options = { buffer = 2 -- default loops to put on end of tweak sheet (min 2) } nBands = band.GetCount() nSeg = structure.GetCount() -- ================================== Begin New Dialog Library Functions -- Add a wall of text from a table function dialog.AddLabels(d,msg,nlabels) -- pass in # of existing autolabels local nlabels = nlabels or #(d._Order or {}) -- default, valid if never delete dialog elements if type(msg) == 'string' then msg = { msg } end for i = 1,#msg do d['autolabel'..tostring(i+nlabels)] = dialog.AddLabel(msg[i]) end end -- Create but don't display a wall of text and 1 or 2 buttons function dialog.CreateMessageBox(msg,title,buttontext1,buttontext0) title = title or '' local d = dialog.CreateDialog(title) dialog.AddLabels(d,msg) buttontext1 = buttontext1 or 'Ok' d.button = dialog.AddButton(buttontext1,1) if buttontext0 ~= nil then d.button0 = dialog.AddButton(buttontext0,0) end return d end -- Display a dialog box function dialog.ShowMessageBox(msg,title,buttontext1,buttontext0) return dialog.Show(dialog.CreateMessageBox(msg,title,buttontext1,buttontext0)) end -- Build a multipage dialog, returns table of dialogs -- pages is a table contain strings or tables of strings -- title is table or string function dialog.CreateBook(pages,title,buttontext1,buttontext0) local dialogs = {} for i = 1,#pages do local t = type(title) == "string" and title or title[i] local d = dialog.CreateMessageBox(pages[i],t,buttontext1,buttontext0) if i > 1 then d.prev = dialog.AddButton("Prev",2) end if i < #pages then d.next = dialog.AddButton("Next",3) end dialogs[#dialogs+1] = d end return dialogs end -- Display a multipage dialog function dialog.ShowBook(dialogs,page) local rc page = page or 1 repeat rc = dialog.Show(dialogs[page]) if rc == 2 then page = page - 1 end if rc == 3 then page = page + 1 end until rc == 1 or rc == 0 return rc end -- ================================== End New Dialog Library Functions -- ================================== Begin User Segment Selection Code -- Determine what the user is going to shift by frozen or selection -- Returns a pair of positive numbers or a negative number -- Valid inputs are: -- a single range selected -- a single range frozen -- 2 frozen segments ERROR_NOSEL, ERROR_BOTH, ERROR_MULT_SEL, ERROR_MULT_FREEZE = -1,-2,-3,-4 function GetSelection() local list1,list2 = {},{} -- build up table of frozen and selected segments for i = 1,nSeg do if selection.IsSelected(i) then list1[#list1+1] = i end if freeze.IsFrozen(i) then list2[#list2+1] = i end end local sel,froz -- determine if the selection is valid if #list1 == 0 then sel = 0 -- none selected elseif list1[#list1]-list1[1] ~= #list1-1 then sel = -1 -- multiple blocks selected else sel = {list1[1],list1[#list1]} -- valid selection range end -- determine if the freezing is valid if #list2 == 0 then froz = 0 -- none frozen elseif list2[#list2]-list2[1] ~= #list2-1 and #list2 ~= 2 then froz = -1 -- multiple blocks frozen else froz = {list2[1],list2[#list2]} -- valid frozen range end -- begin listing error conditions if sel == 0 and froz == 0 then return ERROR_NOSEL end if sel ~= 0 and froz ~= 0 then return ERROR_BOTH end -- at this point, exactly one of sel, froz is not 0 if sel == -1 then return ERROR_MULT_SEL end if froz == -1 then return ERROR_MULT_FREEZE end -- at this point, the non 0 is a valid range if sel == 0 then return froz end return sel end -- ================================== End User Segment Selection Code -- ================================== Begin Recipe Dialogs -- Return a string indicating segment type function GetAminoAcidSafe(iSeg) if structure.GetSecondaryStructure(iSeg) == "M" then return "ligand" end return structure.GetAminoAcid(iSeg) end -- Return a string giving distance to next segment function GetNeighborDistance(iSeg) if iSeg >= nSeg or iSeg < 1 then return "0" end return string.format("%0.5f",structure.GetDistance(iSeg,iSeg+1)) end helpdialogs = nil function HelpDialog(page) if helpdialogs == nil then helpdialogs = dialog.CreateBook( { {"This script will help you shift a long part of the", "protein using the tweak tool. First, use either", "selection or freeze to mark all the segments you want", "to shift. The script will turn all of those into", "sheets. It will also add at least 2 loops to both ends,", "which are required by tweak. It will save the original", "structure in the notes for later (you might want to", "hide those.)", "", "Click Start to begin changing the structure. The shift", "will work best if you place cutpoints (optional) at", "least *3* segments from the tweak zone." }, {"After running the script the first time, you can now", "use the tweak tool on the sheets to shift them. Don't", "shift too much, leave some overlap with your original", "position. After you have shifted, run the script again.", "The script should automatically determine how far you", "shifted. If it doesn't, you can set it in the box. If", "you shifted segments to the positions of lower", "numbered segments, that's a negative shift.", "", "Click Finish to apply the original secondary", "structure to is original positions (not segments) or", "Abort to clear all notes and structure." }, },{ "Power Shift (stage 1)", "Power Shift (stage 2)" },"Return") end dialog.ShowBook(helpdialogs,page) end START,FINISH,CANCEL,HELP,ERROR,ABORT = 1,2,0,4,5,6 function Stage1Dialog() d = dialog.CreateDialog(title.." Stage 1") d.label1 = dialog.AddLabel(string.format( "It seems you have selected %d (%s) - %d (%s)", range[1],GetAminoAcidSafe(range[1]), range[2],GetAminoAcidSafe(range[2]) )) dialog.AddLabels(d, "Extra loops are required for shift to function.") d.buffer = dialog.AddSlider("Extra Loops",options.buffer,2,5,0) dialog.AddLabels(d, {"[1) Select Range, then Click Start.", "2) Tweak sheet segments and shift", "3) Run recipe, Click Finish.", "DO NOT ALTER BANDS OR WIGGLE"}) d.start = dialog.AddButton("Start",START) d.cancel = dialog.AddButton("Cancel",CANCEL) d.help = dialog.AddButton("Help",HELP) local rc repeat rc = dialog.Show(d) if rc == HELP then HelpDialog(1) end until rc ~= HELP options.buffer = d.buffer.value return rc end function Stage2Dialog() d = dialog.CreateDialog(title.." Stage 2") d.label1 = dialog.AddLabel(string.format( "It seems you are tweaking %d - %d (including buffer)", options.first,options.last)) if options.shift then d.label2 = dialog.AddLabel("It seems you have shifted:") else d.label2 = dialog.AddLabel("Could not autodetect your shift, please enter:") end d.shift = dialog.AddTextbox("Shift +-n:",tostring(options.shift or 0)) dialog.AddLabels(d, {"Tip: set to 0 to make structure shift with segments!", "Click CANCEL to Shift more, FINISH to apply structure,", "or ABORT to clear all notes and exit."}) d.finish = dialog.AddButton("Finish",FINISH) d.cancel = dialog.AddButton("Cancel",CANCEL) d.abort = dialog.AddButton("Abort",ABORT) d.help = dialog.AddButton("Help",HELP) local rc repeat rc = dialog.Show(d) if rc == HELP then HelpDialog(2) elseif rc ~= CANCEL and rc ~= ABORT then-- error check input local rc2,val = pcall( -- pull a single number out of textbox, if possible function(s) return tonumber(select(3,s:find("(%-?%d+)"))) end, d.shift.value) if rc2 == true then options.shift = val else dialog.ShowMessageBox({ "Please specify how many segments you shifted,", "and direction (negative shifted to lower seg)."} ,"User Entry Error") rc = ERROR end end until rc ~= HELP and rc ~= ERROR return rc end -- Get the user selection and show error message if invalid function GetSelectionDialog() local range = GetSelection() if type(range)=="number" then local errormsg = { {"You need to select the region to shift by selecting", "(in selection interface) or freezing two segments or", "a single group (in original interface)."}, "Use only selection or only freeze, not both.", "You seem to have multiple regions selected.", "You seem to have multiple regions frozen." } local d = dialog.CreateMessageBox(errormsg[-range],"Range Selection Error","Okay","Clear S/F") d.help = dialog.AddButton("Help",2) repeat local rc = dialog.Show(d) if rc == 0 then selection.DeselectAll() freeze.UnfreezeAll() end if rc == 2 then HelpDialog() end until rc ~= 2 return end return range end -- ================================== End Recipe Dialogs -- ================================== Begin Note Routines -- These routines parse a data string independent of pre-existing -- note contents. Data is stored as "Tag <data>", where Tag is -- recipe-specific string, the < > are literal delimiters, and -- data is an arbitrary string that does not contain ">". --FieldWrite = "%s <%s %s>" -- write our tag, a char, and a number --FieldRead = "<(.) (.+)>" -- read a char and number --FieldWrite = "%s <%s>" -- write our tag and a single char --FieldRead = "<(.)>" -- read a single char FieldWrite1 = "%s <%s>" FieldWrite2 = "%s <%s %d>" FieldRead = "<(.) -(%d*)>" -- char and optional number -- Regular expressions for parsing our note data FindPattern = NoteTag -- find our tag ExtractPattern = NoteTag.." (%b<>)" -- extract the part in <> ReplacePattern = NoteTag.." %b<>" -- replace the entire data DeletePattern = " *"..ReplacePattern -- delete data and leading spaces -- returns true if any recipe notes exist function IsStage2() for i = 1,nSeg do local s = structure.GetNote(i) if s:find(FindPattern) then return true,i end end return false,0 end -- Add this recipe's data to a segment note function SetRecipeNote(iSeg,extra) local s,t s = structure.GetNote(iSeg) if extra then t = string.format(FieldWrite2,NoteTag,structure.GetSecondaryStructure(iSeg),extra) -- our data else t = string.format(FieldWrite1,NoteTag,structure.GetSecondaryStructure(iSeg)) -- our data end if s:find(FindPattern) then s = s:gsub(ReplacePattern,t) -- replace our data elseif s == "" then s = t -- new note else s = string.format("%s %s",s,t) -- append our data end structure.SetNote(iSeg,s) end -- Get this recipe's data from a segment note, or nil function GetRecipeNote(iSeg) local s,t s = structure.GetNote(iSeg) _,_, s = s:find(ExtractPattern) -- find our tag and data if s == nil then return end _,_,s,t = s:find(FieldRead) -- parse data component return s,t end -- Remove this recipe's data from all the notes function RemoveRecipeNotes() for i = 1,nSeg do local s = structure.GetNote(i) if s ~= "" then s = s:gsub(DeletePattern,"") -- remove our data if present and leading space structure.SetNote(i,s) end end end -- Extract all the notes into a table -- Pass in result from IsStage2() to save time -- Notes must already exist as a continuous block function GetRecipeNotesBlock(first) notes = {} last = nSeg for i = first,nSeg do notes[i] = GetRecipeNote(i) if notes[i] == nil then last = i - 1 break end end return notes,last end -- ================================== End Note Routines -- Create a 'zero'length band ZLBLen = 0.0001 function ZeroLengthBand(iSeg) local s2,s3 if iSeg == 1 then s2,s3 = 2, 3 elseif iSeg == nSeg then s2,s3 = iSeg-1,iSeg-2 else s2,s3 = iSeg-1,iSeg+1 end local iBand = band.Add(iSeg,s2,s3,ZLBLen,0,0) band.Disable(iBand) return iBand end -- Sets a region to sheets with loops on either end function SetSSForTweak(first,start,finish,last) selection.DeselectAll() selection.SelectRange(start,finish) structure.SetSecondaryStructureSelected('E') selection.DeselectAll() if first < start then selection.SelectRange(first,start-1) end if last > finish then selection.SelectRange(finish+1,last) end structure.SetSecondaryStructureSelected('L') selection.DeselectAll() end -- Apply the original secondary structure, but shifted back to -- the original positions in space -- structures is a table of structures indexed by seg number function RestoreSecondaryStructure(first,last,structures,shift) shift = shift or 0 selection.SelectRange(first,last) structure.SetSecondaryStructureSelected('L') -- default loops function Apply(seg1,seg2,s) selection.DeselectAll() selection.SelectRange(seg1,seg2) structure.SetSecondaryStructureSelected(s) end -- Using ranges is more complicated, but much faster due to drawing time local s,start,n for i = first,last do if notes[i+options.shift] then if s == nil then s,start,n = notes[i+options.shift], i, 0 -- init range elseif s == notes[i+options.shift] then n = n + 1 -- expand range else Apply(start,start+n,s) s,start,n = notes[i+options.shift], i, 0 -- init new range end -- if s elseif s then -- if notes Apply(start,start+n,s) s = nil end end if s then Apply(start,start+n,s) end end -- Find the seg closest to one of the original endpoints function EstimateShift() local BandToFirst = nBands-1 local BandToLast = nBands local threshold = 0.2 local n = options.last-options.first for i = 0,n do band.AddToBandEndpoint(i+options.first,BandToFirst) band.AddToBandEndpoint(n-i+options.first,BandToLast) local d1,d2 = band.GetLength(band.GetCount()-1), band.GetLength(band.GetCount()) if d1 < threshold then return -i + options.buffer, d1 end if d2 < threshold then return i - options.buffer, d2 end end return nil end -- Delete added bands, optionally, the ZLB bands as well function DeleteAddedBands(zlb) local keep = nBands -- bands to keep (pre-existing bands) if zlb then keep = keep - 2 end if keep <= 0 then band.DeleteAll() else while band.GetCount() > keep do band.Delete(band.GetCount()) end end end -- ================================== Begin Main print(title) print(puzzle.GetName(),os.date()) stage2,first = IsStage2() -- look for notes with our tag if not stage2 then -- stage 1 print("Stage 1: Initial setup") range = GetSelectionDialog() if not range then return end rc = Stage1Dialog() if rc == CANCEL then print("Cancelled.") return end freeze.UnfreezeAll() first = math.max(1,range[1]-options.buffer) last = math.min(nSeg,range[2]+options.buffer) SetRecipeNote(first,options.buffer) -- save the buffer length into first note for i = first+1,last do -- save just the structure in the rest of the notes SetRecipeNote(i) end print("Setting secondary structure from",first,"to",last) print("With sheets from",range[1],"to",range[2]) SetSSForTweak(first,range[1],range[2],last) print("Adding two bands at",range[1],"and",range[2]) ZeroLengthBand(range[1]) ZeroLengthBand(range[2]) selection.SelectRange(first,last) -- reselect for selection interface print("Sheets re-selected.") print("Suggest you create cutpoints BEYOND the buffer.") print("Also, quicksave before and after tweaking.") print("Don't wiggle or change bands.") else -- stage 2 print("Stage 2: Restoring structure") notes,last = GetRecipeNotesBlock(first) _,buffer = GetRecipeNote(first) print("Buffer =",buffer) options.first = first options.last = last if nBands >= 2 then print("Autodetecting shift....") options.shift, atom_dist = EstimateShift() DeleteAddedBands(false) if options.shift then print(string.format("Autodetected shift as %d (atom offset %0.3f)",options.shift,atom_dist)) end auto_bands_exist = true else print("Bands missing, no autodetect.") auto_bands_exist = false end rc = Stage2Dialog() if rc == CANCEL then print("Cancelled.") return end if rc == ABORT then print("Aborting...removing added notes, bands, selection, and freeze.") selection.DeselectAll() freeze.UnfreezeAll() RemoveRecipeNotes() DeleteAddedBands(false) print("Done.") return end print("User confirms shift =",options.shift) print("Restoring original secondary structure...") RestoreSecondaryStructure(first,last,notes,options.shift) print("Clearing bands, notes, and selection...") selection.DeselectAll() DeleteAddedBands(auto_bands_exist) RemoveRecipeNotes() print("Done.") end

Comments


brow42 Lv 1

The Sheet Tweak tool works almost exactly like the alignment tool. In fact, I'm sure they use the same code. You can use the tweak tool to shift your protein along its current shape exactly, even if your protein isn't a simple sheet. Normally the tweak tool has to arrange the loops that you didn't tweak after the shift, which can make the tweak fail. If you also use the cut tool, then this isn't necessary.

This is more like a wizard than a script; it can't be fully automated. You can do the shift by hand by converting everything you want to shift to sheet, making cuts, tweaking, closing cuts, and then reapplying the original secondary structure (to the shape, not to the segments). This script handles all the structure changes; you still have to do the cutpoints and tweak yourself. You run the script once to set up the tweak, and then you run it again to clean up the structure.

How to run:
First, tell the script what you want to tweak but selecting or freezing your region of interest. Double-click freezing can be handy here. There must be one single continuous frozen/selected region. Alternatively, you can freeze exactly 2 segments and it will include all the segments between. If the script is confused, you get a dialog and an option to clear all freezes and selections (which could exist invisibly to original interface users). Just clear, reselect/refreeze and run it again.

The script will set your region to sheet, set the required buffer region to loop, record the original structure in the notes, and make a pair of bands at the ends. Your region will be re-selected for you.

Make cutpoints outside the buffer region (optional). Use the tweak tool on the sheet to shift the sheet. Be careful about which arrow you click (see bottom of post)! Quicksave before and after tweak would be a good idea.

Run the script again. It will attempt to autodetect how far you shifted; you can also set this number yourself. If you shift segments to occupy the space of lower numbered segments, that is a negative shift. You can cancel out and make no changes, or click finish.

Finish will re-apply all the saved structure, but to the original positions in space instead of the original segments. It will then remove the added notes and added bands (the original notes will remain).

If you want to start a new shift, but it goes to the finalize part instead, you can click abort, and it will clean up the notes and turn everything into loop.

Limitations:

  • Your shift region needs to overlap so that the auto shift detection will work. If you shift beyond that, you'll have to enter the shift by hand.
  • Autodetection looks at atomic-band distances, so don't wiggle, tweak rotate, or anything else that changes the backbone positions.
  • Cutting is optional, but the tweak will eventually fail when you run out of slack space (a large buffer will help). Also, not using cutpoints will increase the distortion and hurt autodetection since the protein is becoming distorted.
  • Not really tested: really long shifts, shifts near protein ends, shifts containing cutpoints, shifts involving ligands, shifting back and forth, selection interface. Be warned. </ul> Things I discovered about tweak sheets:
    • The atom positions are not exactly the same!
    • You need at least 2 loops on each end outside the tweak region or the tweak will fail (wiki says 3).
    • The cutpoints must be at least three away or it will not thread properly.
    • The tweak tool will be located in the center of the tweak region, but the direction arrows relate to something else, perhaps the average orientation. This can be confusion in something like a zigzagging sheet, where the arrows could point in the OPPOSITE direction of the shift of the segment directly below the tool.
    • Using the rotate function rotates the entire group about its center, like the move tool. It doesn't rotate segments. </ul>