int DexApparatus::CheckCorrectStartPosition( int target_id, float tolX, float tolY, float tolZ, int max_bad_positions, const char *msg, const char *picture ) { const char *fmt; bool error = false; int bad_positions = 0, movements = 0; int first, last; int i, index; Vector3 delta; // First we should look for the start and end of the actual movement based on // events such as when the subject reaches the first target. FindAnalysisFrameRange( first, last ); // Step through each marked movement trigger and verify that the velocity is // close to zero when the trigger was sent. FindAnalysisEventRange( first, last ); for ( i = first; i < last; i++ ) { if ( eventList[i].event == TRIGGER_MOVEMENT ) { movements++; index = TimeToFrame( eventList[i].time ); SubtractVectors( delta, acquiredManipulandumState[index].position, targetPosition[target_id] ); if ( fabs( delta[X] ) > tolX || fabs( delta[Y] ) > tolY || fabs( delta[Z] ) > tolZ ) bad_positions++; } } // Check if the computed number of incorrect starting positions is in the desired range. error = ( bad_positions > max_bad_positions ); // If not, signal the error to the subject. // Here I take the approach of calling a method to signal the error. // We agree instead that the routine should either return a null pointer // if there is no error, or return the error message as a static string. if ( error ) { // If the user provided a message to signal a visibilty error, use it. // If not, generate a generic message. if ( !msg ) msg = "Starting position not respected."; // The message could be different depending on whether the maniplulandum // was not moved enough, or if it just could not be seen. fmt = "%s\n Total Movements: %d\n Erroneous Positions: %d\nMaximum Allowed: %d"; int response = fSignalError( MB_ABORTRETRYIGNORE, picture, fmt, msg, movements, bad_positions, max_bad_positions ); if ( response == IDABORT ) return( ABORT_EXIT ); if ( response == IDRETRY ) return( RETRY_EXIT ); if ( response == IDIGNORE ) return( IGNORE_EXIT ); } // This is my means of signalling the event. monitor->SendEvent( fmt, "Start positions OK.", movements, bad_positions, max_bad_positions ); return( NORMAL_EXIT ); }
/** \fn isKeyFrameByTime \brief Return true if frame with PTS==seektime is a keyframe */ bool ADM_EditorSegment::isKeyFrameByTime(uint32_t refVideo,uint64_t seekTime) { uint32_t frame; uint32_t flags; _VIDEOS *v=getRefVideo(refVideo); ADM_assert(v); if(false==TimeToFrame(v,seekTime,&frame,&flags)) return false; if(flags & AVI_KEY_FRAME) return true; return false; }
/** \fn dtsFromPts \brief guestimate DTS from PTS. For the wanted frame, we go back until we find a valid DTS then the wanted DTS=~ valid DTS + timeIncrement * number of frames in between */ bool ADM_EditorSegment::dtsFromPts(uint32_t refVideo,uint64_t pts,uint64_t *dts) { uint32_t frame,flags; _VIDEOS *vid=getRefVideo(refVideo); vidHeader *demuxer=vid->_aviheader; if(false==TimeToFrame(vid,pts,&frame,&flags)) { ADM_warning("Cannot get frame with pts=%"PRIu64" ms\n",pts/1000); return false; } // Now get DTS.. uint64_t p,d; if(!frame) // The very first frame { demuxer->getPtsDts(0,&p,&d); if(d==ADM_NO_PTS) { ADM_warning("No DTS available for first frame, putting pts, probably incorrect\n"); *dts=pts; }else { *dts=d; } return true; } int32_t deltaFrame=frame; while(deltaFrame>0) { demuxer->getPtsDts(deltaFrame,&p,&d); if(d!=ADM_NO_PTS) break; deltaFrame--; } if(deltaFrame<0) { ADM_warning("Cannot find a valid DTS for pts=%"PRIu64"ms\n",pts/1000); *dts=pts; return false; } deltaFrame=frame-deltaFrame; *dts=d+deltaFrame*vid->timeIncrementInUs; return true; }
/** \fn intraTimeToFrame \brief Return the frame whosePTS==seektime, assert if does not exist */ uint32_t ADM_EditorSegment::intraTimeToFrame(uint32_t refVideo,uint64_t seekTime) { uint32_t frame; uint32_t flags; _VIDEOS *v=getRefVideo(refVideo); ADM_assert(v); if(false==TimeToFrame(v,seekTime,&frame,&flags)) { ADM_error("Cannot find frame with time %"PRIu64"ms\n",seekTime/1000); ADM_assert(0); } uint32_t next; v->_aviheader->getFlags(frame+1,&next); if(!((next | flags) & AVI_KEY_FRAME)) // The 2nd field might be keyframe { ADM_warning("Seeking to a non keyframe (time=%s), flags=%x, flagsNext=%x\n",ADM_us2plain(seekTime),flags,next); ADM_warning("This is not normal unless you start frame is not a keyframe\n"); } return frame; }
TSharedRef<SWidget> FSequencerTimeSliderController::OpenSetPlaybackRangeMenu(float MouseTime) { const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, nullptr); FText NumericText; if (SequencerSnapValues::IsTimeSnapIntervalFrameRate(TimeSliderArgs.Settings->GetTimeSnapInterval()) && TimeSliderArgs.Settings->GetShowFrameNumbers()) { NumericText = FText::Format(LOCTEXT("FrameTextFormat", "Playback Range (at frame {0}):"), FText::AsNumber(TimeToFrame(MouseTime))); } else { NumericText = FText::Format(LOCTEXT("TimeTextFormat", "Playback Range (at {0}s):"), FText::AsNumber(MouseTime)); } TRange<float> PlaybackRange = TimeSliderArgs.PlaybackRange.Get(); MenuBuilder.BeginSection("SequencerPlaybackRangeMenu", NumericText); { MenuBuilder.AddMenuEntry( FText::Format(LOCTEXT("SetPlaybackStart", "Set Start Time"), NumericText), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=]{ return SetPlaybackRangeStart(MouseTime); }), FCanExecuteAction::CreateLambda([=]{ return MouseTime <= PlaybackRange.GetUpperBoundValue(); }) ) ); MenuBuilder.AddMenuEntry( FText::Format(LOCTEXT("SetPlaybackEnd", "Set End Time"), NumericText), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=]{ return SetPlaybackRangeEnd(MouseTime); }), FCanExecuteAction::CreateLambda([=]{ return MouseTime >= PlaybackRange.GetLowerBoundValue(); }) ) ); } MenuBuilder.EndSection(); // SequencerPlaybackRangeMenu return MenuBuilder.MakeWidget(); }
int32 FSequencerTimeSliderController::OnPaintTimeSlider( bool bMirrorLabels, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { const bool bEnabled = bParentEnabled; const ESlateDrawEffect::Type DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; TRange<float> LocalViewRange = TimeSliderArgs.ViewRange.Get(); const float LocalViewRangeMin = LocalViewRange.GetLowerBoundValue(); const float LocalViewRangeMax = LocalViewRange.GetUpperBoundValue(); const float LocalSequenceLength = LocalViewRangeMax-LocalViewRangeMin; FVector2D Scale = FVector2D(1.0f,1.0f); if ( LocalSequenceLength > 0) { FScrubRangeToScreen RangeToScreen( LocalViewRange, AllottedGeometry.Size ); const float MajorTickHeight = 9.0f; FDrawTickArgs Args; Args.AllottedGeometry = AllottedGeometry; Args.bMirrorLabels = bMirrorLabels; Args.bOnlyDrawMajorTicks = false; Args.TickColor = FLinearColor::White; Args.ClippingRect = MyClippingRect; Args.DrawEffects = DrawEffects; Args.StartLayer = LayerId; Args.TickOffset = bMirrorLabels ? 0.0f : FMath::Abs( AllottedGeometry.Size.Y - MajorTickHeight ); Args.MajorTickHeight = MajorTickHeight; DrawTicks( OutDrawElements, RangeToScreen, Args ); FPaintPlaybackRangeArgs PlaybackRangeArgs( bMirrorLabels ? FEditorStyle::GetBrush("Sequencer.Timeline.PlayRange_Bottom_L") : FEditorStyle::GetBrush("Sequencer.Timeline.PlayRange_Top_L"), bMirrorLabels ? FEditorStyle::GetBrush("Sequencer.Timeline.PlayRange_Bottom_R") : FEditorStyle::GetBrush("Sequencer.Timeline.PlayRange_Top_R"), 6.f ); LayerId = DrawPlaybackRange(AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, RangeToScreen, PlaybackRangeArgs); float HalfSize = FMath::CeilToFloat(ScrubHandleSize/2.0f); // Draw the scrub handle float XPos = RangeToScreen.InputToLocalX( TimeSliderArgs.ScrubPosition.Get() ); // Should draw above the text const int32 ArrowLayer = LayerId + 2; FPaintGeometry MyGeometry = AllottedGeometry.ToPaintGeometry( FVector2D( XPos-HalfSize, 0 ), FVector2D( ScrubHandleSize, AllottedGeometry.Size.Y ) ); FLinearColor ScrubColor = InWidgetStyle.GetColorAndOpacityTint(); // @todo Sequencer this color should be specified in the style ScrubColor.A = ScrubColor.A*0.75f; ScrubColor.B *= 0.1f; ScrubColor.G *= 0.2f; FSlateDrawElement::MakeBox( OutDrawElements, ArrowLayer, MyGeometry, bMirrorLabels ? ScrubHandleUp : ScrubHandleDown, MyClippingRect, DrawEffects, ScrubColor ); // Draw the current time next to the scrub handle float Time = TimeSliderArgs.ScrubPosition.Get(); FString FrameString; if (SequencerSnapValues::IsTimeSnapIntervalFrameRate(TimeSliderArgs.Settings->GetTimeSnapInterval()) && TimeSliderArgs.Settings->GetShowFrameNumbers()) { float FrameRate = 1.0f/TimeSliderArgs.Settings->GetTimeSnapInterval(); float FrameTime = Time * FrameRate; int32 Frame = SequencerHelpers::TimeToFrame(Time, FrameRate); const float FrameTolerance = 0.001f; if (FMath::IsNearlyEqual(FrameTime, (float)Frame, FrameTolerance)) { FrameString = FString::Printf( TEXT("%d"), TimeToFrame(Time)); } else { FrameString = FString::Printf( TEXT("%.3f"), FrameTime); } } else { FrameString = FString::Printf( TEXT("%.2f"), Time ); } FSlateFontInfo SmallLayoutFont( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf"), 10 ); const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); FVector2D TextSize = FontMeasureService->Measure(FrameString, SmallLayoutFont); // Flip the text position if getting near the end of the view range if ((AllottedGeometry.Size.X - XPos) < (TextSize.X + 14.f)) { XPos = XPos - TextSize.X - 12.f; } else { XPos = XPos + 10.f; } FVector2D TextOffset( XPos, Args.bMirrorLabels ? TextSize.Y-6.f : Args.AllottedGeometry.Size.Y - (Args.MajorTickHeight+TextSize.Y) ); FSlateDrawElement::MakeText( OutDrawElements, Args.StartLayer+1, Args.AllottedGeometry.ToPaintGeometry( TextOffset, TextSize ), FrameString, SmallLayoutFont, Args.ClippingRect, Args.DrawEffects, Args.TickColor ); if (MouseDragType == DRAG_SETTING_RANGE) { float MouseStartPosX = RangeToScreen.InputToLocalX(MouseDownRange[0]); float MouseEndPosX = RangeToScreen.InputToLocalX(MouseDownRange[1]); float RangePosX = MouseStartPosX < MouseEndPosX ? MouseStartPosX : MouseEndPosX; float RangeSizeX = FMath::Abs(MouseStartPosX - MouseEndPosX); FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry( FVector2D(RangePosX, 0.f), FVector2D(RangeSizeX, AllottedGeometry.Size.Y) ), bMirrorLabels ? ScrubHandleDown : ScrubHandleUp, MyClippingRect, DrawEffects, MouseStartPosX < MouseEndPosX ? FLinearColor(0.5f, 0.5f, 0.5f) : FLinearColor(0.25f, 0.3f, 0.3f) ); } return ArrowLayer; } return LayerId; }
void FSequencerTimeSliderController::DrawTicks( FSlateWindowElementList& OutDrawElements, const struct FScrubRangeToScreen& RangeToScreen, FDrawTickArgs& InArgs ) const { float MinDisplayTickSpacing = ScrubConstants::MinDisplayTickSpacing; if (SequencerSnapValues::IsTimeSnapIntervalFrameRate(TimeSliderArgs.Settings->GetTimeSnapInterval()) && TimeSliderArgs.Settings->GetShowFrameNumbers()) { MinDisplayTickSpacing = TimeSliderArgs.Settings->GetTimeSnapInterval(); } const float Spacing = DetermineOptimalSpacing( RangeToScreen.PixelsPerInput, ScrubConstants::MinPixelsPerDisplayTick, MinDisplayTickSpacing ); // Sub divisions // @todo Sequencer may need more robust calculation const int32 Divider = 10; // For slightly larger halfway tick mark const int32 HalfDivider = Divider / 2; // Find out where to start from int32 OffsetNum = FMath::FloorToInt(RangeToScreen.ViewInput.GetLowerBoundValue() / Spacing); FSlateFontInfo SmallLayoutFont( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf"), 8 ); TArray<FVector2D> LinePoints; LinePoints.AddUninitialized(2); float Seconds = 0; while( (Seconds = OffsetNum*Spacing) < RangeToScreen.ViewInput.GetUpperBoundValue() ) { // X position local to start of the widget area float XPos = RangeToScreen.InputToLocalX( Seconds ); uint32 AbsOffsetNum = FMath::Abs(OffsetNum); if ( AbsOffsetNum % Divider == 0 ) { FVector2D Offset( XPos, InArgs.TickOffset ); FVector2D TickSize( 0.0f, InArgs.MajorTickHeight ); LinePoints[0] = FVector2D( 0.0f,1.0f); LinePoints[1] = TickSize; // lines should not need anti-aliasing const bool bAntiAliasLines = false; // Draw each tick mark FSlateDrawElement::MakeLines( OutDrawElements, InArgs.StartLayer, InArgs.AllottedGeometry.ToPaintGeometry( Offset, TickSize ), LinePoints, InArgs.ClippingRect, InArgs.DrawEffects, InArgs.TickColor, bAntiAliasLines ); if( !InArgs.bOnlyDrawMajorTicks ) { FString FrameString; if (SequencerSnapValues::IsTimeSnapIntervalFrameRate(TimeSliderArgs.Settings->GetTimeSnapInterval()) && TimeSliderArgs.Settings->GetShowFrameNumbers()) { FrameString = FString::Printf( TEXT("%d"), TimeToFrame(Seconds)); } else { FrameString = Spacing == ScrubConstants::MinDisplayTickSpacing ? FString::Printf( TEXT("%.3f"), Seconds ) : FString::Printf( TEXT("%.2f"), Seconds ); } // Space the text between the tick mark but slightly above const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); FVector2D TextSize = FontMeasureService->Measure(FrameString, SmallLayoutFont); FVector2D TextOffset( XPos + 5.f, InArgs.bMirrorLabels ? 3.f : FMath::Abs( InArgs.AllottedGeometry.Size.Y - (InArgs.MajorTickHeight+3.f) ) ); FSlateDrawElement::MakeText( OutDrawElements, InArgs.StartLayer+1, InArgs.AllottedGeometry.ToPaintGeometry( TextOffset, TextSize ), FrameString, SmallLayoutFont, InArgs.ClippingRect, InArgs.DrawEffects, InArgs.TickColor*0.65f ); } } else if( !InArgs.bOnlyDrawMajorTicks ) { // Compute the size of each tick mark. If we are half way between to visible values display a slightly larger tick mark const float MinorTickHeight = AbsOffsetNum % HalfDivider == 0 ? 6.0f : 2.0f; FVector2D Offset(XPos, InArgs.bMirrorLabels ? 0.0f : FMath::Abs( InArgs.AllottedGeometry.Size.Y - MinorTickHeight ) ); FVector2D TickSize(0.0f, MinorTickHeight); LinePoints[0] = FVector2D(0.0f,1.0f); LinePoints[1] = TickSize; const bool bAntiAlias = false; // Draw each sub mark FSlateDrawElement::MakeLines( OutDrawElements, InArgs.StartLayer, InArgs.AllottedGeometry.ToPaintGeometry( Offset, TickSize ), LinePoints, InArgs.ClippingRect, InArgs.DrawEffects, InArgs.TickColor, bAntiAlias ); } // Advance to next tick mark ++OffsetNum; } }
int DexApparatus::CheckMovementDirection( int max_false_directions, float dirX, float dirY, float dirZ, float threshold, const char *msg, const char *picture ) { const char *fmt; bool error = false; int bad_movements = 0, movements = 0, starts = 0; int first, last; int i, index; Vector3 dir; dir[X] = dirX; dir[Y] = dirY; dir[Z] = dirZ; float displacement; Vector3 start_position, delta; // First we should look for the start and end of the actual movement based on // events such as when the subject reaches the first target. FindAnalysisFrameRange( first, last ); // Step through each marked upward movement trigger and count if // the initial movement was in the right direction. FindAnalysisEventRange( first, last ); for ( i = first; i < last; i++ ) { if ( eventList[i].event == TRIGGER_MOVE_UP ) { movements++; index = TimeToFrame( eventList[i].time ); CopyVector( start_position, acquiredManipulandumState[index].position ); while ( index < nAcqFrames ) { SubtractVectors( delta, acquiredManipulandumState[index].position, start_position ); displacement = DotProduct( dir, delta ); // 'UP' here means in the same direction and the specified vector. // If we move past the threshold in that direction before moving past the // same threshold distance in the other direction, then this movement is good. if ( displacement > threshold ) { starts++; break; } // If we go the threshold distance in the opposite direction first, // then consider this to have been an erroneous start. if ( displacement < - threshold ) { bad_movements++; starts++; break; } index++; } } else if ( eventList[i].event == TRIGGER_MOVE_DOWN ) { // Now do the same thing for downward movements. movements++; index = TimeToFrame( eventList[i].time ); CopyVector( start_position, acquiredManipulandumState[index].position ); while ( index < nAcqFrames ) { SubtractVectors( delta, acquiredManipulandumState[index].position, start_position ); displacement = DotProduct( dir, delta ); // Here, a negative movement is good ... if ( displacement < - threshold ) { starts++; break; } // ... and a positive movement is bad. if ( displacement > threshold ) { bad_movements++; starts++; break; } index++; } } } // Check if the computed number of incorrect starting positions is in the desired range. error = ( bad_movements > max_false_directions ); // This format string is used to add debugging information to the event notification. // It is used whether there is an error or not. fmt = "%s\n Total Movements: %d\n Actual Starts: %d\n Errors Detected: %d\n Maximum Allowed: %d"; // If not, signal the error to the subject. // Here I take the approach of calling a method to signal the error. // We agree instead that the routine should either return a null pointer // if there is no error, or return the error message as a static string. if ( error ) { // If the user provided a message to signal a visibilty error, use it. // If not, generate a generic message. if ( !msg ) msg = "To many starts in wrong direction."; int response = fSignalError( MB_ABORTRETRYIGNORE, picture, fmt, msg, movements, starts, bad_movements, max_false_directions ); if ( response == IDABORT ) return( ABORT_EXIT ); if ( response == IDRETRY ) return( RETRY_EXIT ); if ( response == IDIGNORE ) return( IGNORE_EXIT ); } // This is my means of signalling the event to ground. monitor->SendEvent( fmt, "Start directions OK.", movements, starts, bad_movements, max_false_directions ); return( NORMAL_EXIT ); }
int DexApparatus::CheckEarlyStarts( int max_early_starts, float hold_time, float threshold, float filter_constant, const char *msg, const char *picture ) { const char *fmt; bool error = false; int early_starts = 0; int first, last; int i, j, index, frm; int hold_frames = (int) floor( hold_time / tracker->samplePeriod ); int N = 0; double tangential_velocity[DEX_MAX_MARKER_FRAMES]; Vector3 delta; // First we should look for the start and end of the actual movement based on // events such as when the subject reaches the first target. FindAnalysisFrameRange( first, last ); // Compute the instantaneous tangential velocity. for ( i = first + 1; i < last; i++ ) { if ( acquiredManipulandumState[i].visibility && acquiredManipulandumState[i-1].visibility) { SubtractVectors( delta, acquiredManipulandumState[i].position, acquiredManipulandumState[i-1].position ); ScaleVector( delta, delta, 1.0 / tracker->GetSamplePeriod() ); tangential_velocity[i] = VectorNorm( delta ); N++; } else { tangential_velocity[i] = tangential_velocity[i-1]; } } // If there is no valid position data, signal an error. if ( N <= 0.0 ) { monitor->SendEvent( "No valid data." ); error = true; } else { // Smooth the tangential velocity using a recursive filter. for ( frm = 1; frm < nAcqFrames; frm++ ) { tangential_velocity[frm] = ( filter_constant * tangential_velocity[frm-1] + tangential_velocity[frm] ) / ( 1.0 + filter_constant ); } // Run the filter backwards to eliminate the phase lag. for ( frm = nAcqFrames - 2; frm >= 0; frm-- ) { tangential_velocity[frm] = ( filter_constant * tangential_velocity[frm+1] + tangential_velocity[frm] ) / ( 1.0 + filter_constant ); } // Step through each marked movement trigger and verify that the velocity is // close to zero when the trigger was sent. FindAnalysisEventRange( first, last ); for ( i = first; i < last; i++ ) { if ( eventList[i].event == TRIGGER_MOVEMENT ) { index = TimeToFrame( eventList[i].time ); for ( j = index; j > index - hold_frames && j > first; j-- ) { if ( tangential_velocity[i] > threshold ) { early_starts++; break; } } } } // Check if the computed number of cycles is in the desired range. error = ( early_starts > max_early_starts ); } // If not, signal the error to the subject. // Here I take the approach of calling a method to signal the error. // We agree instead that the routine should either return a null pointer // if there is no error, or return the error message as a static string. if ( error ) { // If the user provided a message to signal a visibilty error, use it. // If not, generate a generic message. if ( !msg ) msg = "To many false starts."; // The message could be different depending on whether the maniplulandum // was not moved enough, or if it just could not be seen. if ( N <= 0.0 ) fmt = "%s\n Manipulandum not visible."; else fmt = "%s\n False Starts Detected: %d\nMaximum Allowed: %d"; int response = fSignalError( MB_ABORTRETRYIGNORE, picture, fmt, msg, early_starts, max_early_starts ); if ( response == IDABORT ) return( ABORT_EXIT ); if ( response == IDRETRY ) return( RETRY_EXIT ); if ( response == IDIGNORE ) return( IGNORE_EXIT ); } // This is my means of signalling the event. monitor->SendEvent( "Early Starts OK.\n Measured: %d\n Maximum Allowed: %d", early_starts, max_early_starts ); return( NORMAL_EXIT ); }