Event segmentation task

PennController for IBEX Forums Support Event segmentation task

This topic contains 4 replies, has 2 voices, and was last updated by Avatar diana.gomz 1 week, 4 days ago.

Viewing 5 posts - 1 through 5 (of 5 total)
  • Author
    Posts
  • #7167
    Avatar
    diana.gomz
    Participant

    Hello Jeremy,

    We’re designing an event segmentation task for which we’ve created some videos. We’d like to ask participants to indicate the points at which a meaningful event ends and a new one begins and we’d like to do this by having participants use the scrollbar in the video controls instead of choosing from a dropdown list, which is rather tedious as you can see:

    //This is the segmentation task
    Template ("all_4_no_interference_fam_lang_task.csv",
        row => newTrial("segmentation_task",
            newText("info1", "<p>If you were to divide this movie into meaningful events, at which second would the first meaninful event intuitively end?</p>")
                .center()
                .bold()
                .print("center at 50vw", "middle at 20vh")
            ,
            newTimer("wait", 3000)
                .start()
                .wait()
            ,
            newVideo("video", row.video)
                .size("40vw", "23vw")
                .print("center at 50vw","middle at 40vh")
                //.disable(0.01)
                .play()
                .wait()
                .log()
            ,
           // newTooltip("tips", "Scroll over the video to reveal video controls.")
           //    .frame()
           //    .label("Okay")
           //    .position("center right")
           //    .print( getVideo("video") )
           // ,
            newDropDown("slider1", "(Select an option)")
                .center()
                .add("0","1","2","3","4","5","6","7","8","9","10")
                .before(newText("lenght_label", "The natural ending for the first meaningful event would be at: "))
                .after(newText("seconds_label", " seconds."))           
                .print("center at 50vw","middle at 60vh")
                .wait()
                .log()
            ,
            newDropDown("slider2", "(Select an option)")
                .center()
                .add("Yes","No")
                .before(newText("question", "Do you think a new event begins right after the previous one?") )
                .print("center at 50vw", "middle at 65vh")
                .wait()
                .log()
            ,
            getDropDown("slider2")
                .test.selected("Yes")
                    .success(newDropDown("slider3", "(Select an option)")
                                .center()
                                .add("0","1","2","3","4","5","6","7","8","9","10")
                                .before(newText("lenght_label", "The next meaningful event begins at: ") )
                                .after(newText("seconds_label", " seconds."))
                                .print("center at 50vw","middle at 70vh")
                                .wait()
                                .log())
                    //.failure(end())
            ,
            newText("instructions", "Please briefly describe the contents of the video!")
                .print("center at 50vw","middle at 75vh")
            ,
            newTextInput("description", "")
                .log()
                .lines(0)
                .size(500, 100)
                .print("center at 50vw","middle at 80vh")
            ,
            newButton("Next")
                .center()
                .css("background-color", "green")
                .css("font-size", "24px")
                .css("border-radius", "15px")
                .css("padding", "15px 25px")
                .css("text-align", "center")
                .css("color", "#fff")
                .css("border", "none")
                .print("center at 50vw", "middle at 95vh")
                .wait()
            )
            .log("condition",row.condition)
            .log("name",row.name)
            .log("goal",row.goal)
            .log("event",row.event)
            .log("bin",row.bin)
            .log("video",row.video)
    )

    Is there a chance this would be possible?

    Here’s a link to the experiment: https://farm.pcibex.net/r/TViLxy/

    Thank you!
    Diana

    #7168
    Jeremy
    Jeremy
    Keymaster

    Hello Diana,

    You could use a Function element to detect seeking events on the video and perform operations accordingly

    Here is a suggestion — I also reworked the print logic, because the visual arrangement appeared off for me

    Template ("all_4_no_interference_fam_lang_task.csv",
        row => newTrial("segmentation_task",
            defaultText.css({'white-space': 'nowrap', margin: '0.5em'}).center()
            ,
            newText("info1", "<p>If you were to divide this movie into meaningful events, at which second would the first meaninful event intuitively end?</p>")
                .bold()
                .print()
            ,
            newTimer("wait", 3000).start().wait()
            ,
            newVideo("video", row.video)
                .size("40vw", "23vw")
                .print()
                .play()
                .wait()
                .log()
            ,
            newTooltip("tips", "Use the navigation bar to provide your answer")
               .label("Okay")
               .position("right middle")
               .size(200,"auto")
               .print( getVideo("video") )
            ,
            newText("lenght_label", "The natural ending for the first meaningful event would be at: ")
                .after( newText("seconds_label1", "...") )
                .print()
            ,
            newVar("ending times", [0]).log()   // This .log() is somewhat redundant with video.log()
            ,
            newFunction( ()=>new Promise(r=>getVideo("video")._element.video.addEventListener("seeking", async e=>{
                await getVar("ending times").set(v=>[...v.slice(0,-1),e.target.currentTime])._runPromises();
                await getText("seconds_label"+getVar("ending times").value.length).text(
                    Math.round(e.target.currentTime) + " seconds."
                )._runPromises();
                r();
            })) ).call()
            ,
            newDropDown("slider2", "(Select an option)")
                .add("Yes","No")
                .before(newText("question", "Do you think a new event begins right after the previous one?") )
                .cssContainer("margin", "0.5em")
                .center()
                .print()
                .log()
                .wait()
                .test.selected("Yes")
                    .success(
                        getTooltip("tips").print( getVideo("video") )
                        ,
                        getVar("ending times").set(v=>[...v,[0]])
                        ,
                        newText("lenght_label", "The next meaningful event begins at: ")
                            .after( newText("seconds_label2", "...") )
                            .print()
                        ,
                        newFunction(()=>new Promise(r=>getVideo("video")._element.video.addEventListener("seeking", r))).call()
                    )
            ,
            newText("instructions", "Please briefly describe the contents of the video!")
                .print()
            ,
            newTextInput("description", "")
                .log()
                .lines(0)
                .size(500, 100)
                .center()
                .print()
            ,
            newButton("Next")
                .css({
                    "background-color": "green",
                    "font-size": "24px",
                    "border-radius": "15px",
                    padding: "15px 25px",
                    "text-align": "center",
                    color: "#fff",
                    border: "none"
                })
                .center()
                .print()
                .wait()
            )
            .log("condition",row.condition)
            .log("name",row.name)
            .log("goal",row.goal)
            .log("event",row.event)
            .log("bin",row.bin)
            .log("video",row.video)
    )
    

    Jeremy

    #7238
    Avatar
    diana.gomz
    Participant

    Hello Jeremy,

    Thanks again for the code. I have two more questions about this project (https://farm.pcibex.net/r/TViLxy/):

    1) Is it possible to use fps instead of seconds? Since our videos are quite short, we realized it doesn’t make much sense to use seconds. We could still use unrounded numbers or maybe calculate fps afterwards, but just in case.

    2) Would it be possible to create a loop to be able to ask participants to segment the videos as many times as they see fit? Here’s one of my failed attempts:

    let launch = [
        getVideo("video") .print() .play() .wait() .log() 
        , 
        getTooltip("tips") .print( getVideo("video") ) 
        , 
        getVar("ending times").set(v=>[...v,[0]]) 
        , 
        newText("length_label", "The next meaningful event begins at: ") .after( newText("seconds_label2", "...") ) .print()    
        , 
        newFunction(()=>new Promise(r=>getVideo("video")._element.video.addEventListener("seeking", r))).call() 
        ,
        getButton("That's about right") .print() .wait() .remove() 
        ,
        getText("info2").remove()
        ,
        getVideo("video").remove() 
        ,
        getText("length_label").remove() 
        , 
        getDropDown("dropdown")     
    ]
    //This is the segmentation task:
    Template ("all_4_no_interference_fam_lang_task.csv", 
        row => newTrial("segmentation_task", defaultText.css({'white-space': 'nowrap', margin: '0.5em'}).center() , 
            newText("info1", "At which second would the first meaningful event of this movie intuitively end?") .bold() .print() 
            , 
            newTimer("wait", 3000).start().wait() 
            , 
            newVideo("video", row.video) .size("40vw", "23vw") .print() .play() .wait() .log() 
            , 
            newTooltip("tips", "Use the navigation bar to provide your answer.") .label("Okay") .position("right middle") .size(200,"auto")  .print( getVideo("video") ) 
            , 
            newText("length_label", "The natural ending for the first meaningful event would be at: ") .after( newText("seconds_label1", "...") ) .print() 
            , 
            newVar("ending times", [0]).log() // This .log() is somewhat redundant with video.log() 
            , 
            newFunction( ()=>new Promise(r=>getVideo("video")._element.video.addEventListener("seeking", async e=>{ await getVar("ending times").set(v=>[...v.slice(0,-1),e.target.currentTime])._runPromises(); await getText("seconds_label"+getVar("ending times").value.length).text( Math.round(e.target.currentTime) + " seconds." )._runPromises(); r(); })) ).call() 
            ,
            newButton("That's about right") .css({ "background-color": "green", "font-size": "24px", "border-radius": "15px", padding: "15px 25px", "text-align": "center", color: "#fff", border: "none" }) .center() .print() .wait() .remove() 
            ,
            getText("info1").remove()
            ,
            getVideo("video").remove() 
            ,
            getText("length_label").remove() 
            , 
            newDropDown("dropdown", "(Select an option)") .add("Yes","No") .before(newText("question", "Do you think a new event begins right after the previous one?") ) .cssContainer("margin", "0.5em") .center() 
            	.print() 
            	.wait( )
            	    .test.selected("No") 
            	        .failure (newText("info2", "At which second would the next meaningful event of this movie intuitively begin?") .bold() .print() 
            	                   , 
            	                   newTimer("wait", 3000).start().wait() 
            	                   , 
            	                   ...launch) 
            ,
            newText("description", "Please briefly describe the contents of the movie.") .print() 
            , 
            newTextInput("description", "") .log() .lines(0) .size(500, 100) .center() .print() 
            , 
            newButton("Next movie") .css({ "background-color": "green", "font-size": "24px", "border-radius": "15px", padding: "15px 25px", "text-align": "center", color: "#fff", border: "none" }) .center() .print() .wait(getTextInput("description").testNot.text("")) 
        )  
        .log("condition",row.condition) .log("name",row.name) .log("goal",row.goal) .log("event",row.event) .log("bin",row.bin) .log("video",row.video) 
    ) 

    Thank you again,
    Diana

    #7240
    Jeremy
    Jeremy
    Keymaster

    Hi Diana,

    1) I am not sure what you mean by FPS. It usually means “frames per second,” which is a measure of how “dense” is video rendering: the denser (higher FPS) the more fluid the video appears, the less dense (lower FPS) the jerkier the video appears. Do you mean using frame numbers instead of seconds to point to positions in the video? I’m not sure it is feasible, and if it is, it wouldn’t be easy. More importantly, I strongly suspect that your participants will have a hard time understanding how to use this alternative cursor, when all they’re given (and which they most likely are already familiar with) are timecodes of the form MM:ss:mm (MM for minutes, mm for milliseconds). I do think using unrounded seconds would be the most straightforward way to go

    2) In this case PennController will not give you all you need for what you want to do out of the box. You will need to create as many new elements as the participant needs, not just elements named “…1” and “…2” as you have now. PennController interprets the newX commands at the beginning of the experiment and not at runtime, so you cannot just use newX to dynamically create an indefinite number of new elements. You would need to wrap those newX commands in Function elements, so they can be interpreted during runtime. Here’s a very simplified illustration of the concept, not actually using a Video element for simplicity:

    newTrial(
        newCanvas("container", "auto", "auto").print()
        ,
        newScale("dummy", 100).slider().print(getCanvas("container"))
        ,
        newVar("time", -1),
        newVar("times", []).log()
        ,
        newFunction( () => document.querySelector(".PennController-dummy input").addEventListener("change", e=>{
            getVar("time").set(e.target.value)._runPromises();
            const textSpan = document.querySelector(".PennController-Text-container:last-child span");
            textSpan.innerHTML = textSpan.innerHTML.replace(/<strong>.*<\/strong>$/, "<strong>"+e.target.value+"</strong>");
        }) ).call()
        ,
        newButton("Add a segment")
            .callback( 
                getVar("time").testNot.is(v=>v<0).success( getVar("times").set(v=>[...v,getVar("time").value]) )
                ,
                newFunction( ()=>new Promise(r => 
                    newText("The natural ending for the first meaningful event would be at: <strong></strong>")
                        .print(getCanvas("container"))
                        ._runPromises().then(r)
                )).call()
            )
            .print()
            .click() // start with one segment already
        ,
        newButton("Finish").print().wait()
        ,
        getVar("time").testNot.is(v=>v<0).success( getVar("times").set(v=>[...v,getVar("time").value]) )
    )

    Note that in this example, you get warning about creating new Function and Text elements with the same names, but those are not fatal to the execution of the program

    Jeremy

    #7241
    Avatar
    diana.gomz
    Participant

    Hi Jeremy,

    Thank you for your suggestions.

    1) Yes, I was referring to frames per second. I think you’re right about the potential confusion for participants. So, I think we’ll go for unrounded seconds.

    2) I see! Well, that helps a lot.

    Diana

Viewing 5 posts - 1 through 5 (of 5 total)

You must be logged in to reply to this topic.