Skip to content
A cognitive science package based on object-oriented programming ideas. The program is driven by the JavaFx framework and the JVM platform.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.idea
src/main
CNAME
LICENSE
README.md
_config.yml
pom.xml

README.md

Psy4J

A cognitive science package based on object-oriented programming ideas. The program is driven by the JavaFx framework and the JVM platform.

Psy4J means Psychology ToolKit For Java Virtual Machine Platform,The program is written in Java 8 and Scala 2.12.6 and built on the JavaFx 2 GUI platform.

Structure

Update Log

Version 1.2.7

增强了 DataUtils 类的功能,修正了一些细节问题。

getSD 拆分为 getSimpleSDgetTotalSD, 将 walkAndProcess 拆分为 walkAndProcess 以及 walkAndProcessAround

后者当遍历完一个文件夹后,统一执行操作,将 doWithFile 拆分为 doWithLinedoWithTable,前者将 Stream[String] 转换为 Stream[D],之后,后者相比前者,又进行了 Stream[D]S 的转换。

因为在实际中发现,或许除了 Data 表示每行的数据之外,还需要 Subject 定义一些对于整张表的方法,比如对每行数据进行的分组策略,每个被试的实验条件信息等。

下面是一个使用 DataUtils 处理,且没有 Subject 层的示例文档:

object DataBatch {

  /**
    * 将每行转换成对应的数据结构 - 必须为 12 行数据,如果为 11 行,则伪造最后一行为全部正确
    *
    * @param line 如果每行不齐,则返回伪造数据,用于最后的对齐,反之,则装载真正数据
    * @return 伪造或者真实的数据,通过 real 属性辨别
    */
  def lineToData(line: String): Option[Data] = 
    if (line.contains("ID, SHOW_TIME,")) return None
    var s = line.split(",").filterNot(_.isEmpty).map(_.trim).map(_.replace("$",""))
    if (s.length != 12) {
      println("Get Wrong Line => " + line)
      if (s.length == 11) s = fakeLine(s)
      else return Option(fakeData)
    }
    val data = Data(
      s(0), s(1).toLong, s(2).toLong, s(3).toLong, s(4).toLong, s(5).toLong, s(6).toLong,
      if (s(7).toInt == 0) false else true, s(8).toLong,
      if (s(9).toInt == 0) false else true, s(10), if (s(11).toInt == 0) false else true
    )
    Option(finalCheck(data))
  }

  def dataToSubject(in: Stream[Data]): Subject = Subject(in.toArray)

  var duration_time_bad_line = 0

  def finalCheck(in:Data):Data = {
    if (in.duration_time_ms < 250) {
      duration_time_bad_line += 1
      in.real = false
      in
    }
    else in
  }

  def fakeLine(s: Array[String]): Array[String] = {
    println(s"Fake Line to ${s.mkString(",") + ",1"}")
    s ++ Array("1")
  }

  def fakeData: Data = Data("0",0L,0L,0L,0L,0L,0L,size_is_big = false,0L, sti_is_left = false,"", answer = false, real = false)
  
  case class Subject(lines: Array[Data])

  def invoke(path: Path, isPreExp:Boolean = false): Path =
    walkAndProcess(path, _.toFile.toString.endsWith(".csv"))(
      f => {
        implicit val sb: StringBuilder = new StringBuilder
        doWithTable(f,
          check = c => {
            println(s"Total Line is ${c.filterNot(_.isEmpty).size}")
            sb.append(s"Total Line is ${c.filterNot(_.isEmpty).size}\n")
          },
          convert = lineToData,
          collect = dataToSubject)(
          s => {
            val i = s.lines
            if (isPreExp) i.foreach(_.isPreExp = true)
            val all_removed_data_length = i.filterNot(_.real).toList.size
            val b = i.toArray.filter(_.real)
            val a = b.filter(_.correct)
            val correct_wrong_count = b.length - a.length
            if (a.forall(_.isPreExp)) {
              val left_late = a.filter(d => d.sti_is_left && d.order_is_late).map(_.duration_time_ms.toDouble)
              val left_early = a.filter(d => d.sti_is_left && !d.order_is_late).map(_.duration_time_ms.toDouble)
              val right_late = a.filter(d => !d.sti_is_left && d.order_is_late).map(_.duration_time_ms.toDouble)
              val right_early = a.filter(d => !d.sti_is_left && !d.order_is_late).map(_.duration_time_ms.toDouble)

              val ll_res = filterInSD(2)(left_late, getSD(left_late))
              val le_res = filterInSD(2)(left_early, getSD(left_early))
              val rl_res = filterInSD(2)(right_late, getSD(right_late))
              val re_res = filterInSD(2)(right_early, getSD(right_early))

              sb.append(s"CHECK_BY = JI_OU\n")
              sb.append(s"For LEFT_LATE: ${ll_res.sum / ll_res.length} ms, SD is ${getSD(ll_res)} ms\n")
              sb.append(s"For LEFT_EARLY: ${le_res.sum / le_res.length} ms, SD is ${getSD(le_res)} ms\n")
              sb.append(s"For RIGHT_LATE: ${rl_res.sum / rl_res.length} ms, SD is ${getSD(rl_res)} ms\n")
              sb.append(s"For RIGHT_EARLY: ${re_res.sum / re_res.length} ms, SD is ${getSD(re_res)} ms\n")
              sb.append("\n\n\n")

            } else if (a.forall(_.check_by == "ORDER")) {
              val left_late = a.filter(d => d.sti_is_left && d.order_is_late).map(_.duration_time_ms.toDouble)
              val left_early = a.filter(d => d.sti_is_left && !d.order_is_late).map(_.duration_time_ms.toDouble)
              val right_late = a.filter(d => !d.sti_is_left && d.order_is_late).map(_.duration_time_ms.toDouble)
              val right_early = a.filter(d => !d.sti_is_left && !d.order_is_late).map(_.duration_time_ms.toDouble)

              val ll_res = filterInSD(2)(left_late, getSD(left_late))
              val le_res = filterInSD(2)(left_early, getSD(left_early))
              val rl_res = filterInSD(2)(right_late, getSD(right_late))
              val re_res = filterInSD(2)(right_early, getSD(right_early))

              sb.append(s"CHECK_BY = ORDER\n")
              sb.append(s"For LEFT_LATE: ${ll_res.sum / ll_res.length} ms, SD is ${getSD(ll_res)} ms\n")
              sb.append(s"For LEFT_EARLY: ${le_res.sum / le_res.length} ms, SD is ${getSD(le_res)} ms\n")
              sb.append(s"For RIGHT_LATE: ${rl_res.sum / rl_res.length} ms, SD is ${getSD(rl_res)} ms\n")
              sb.append(s"For RIGHT_EARLY: ${re_res.sum / re_res.length} ms, SD is ${getSD(re_res)} ms\n")
              sb.append("\n\n\n")
            } else if (a.forall(_.check_by == "SIZE")) {
              val big_left = a.groupBy(d => d.sti_is_left && d.size_is_big)(true).map(_.duration_time_ms.toDouble)
              val big_right = a.groupBy(d => !d.sti_is_left && d.size_is_big)(true).map(_.duration_time_ms.toDouble)
              val small_left = a.groupBy(d => d.sti_is_left && !d.size_is_big)(true).map(_.duration_time_ms.toDouble)
              val small_right = a.groupBy(d => !d.sti_is_left && !d.size_is_big)(true).map(_.duration_time_ms.toDouble)

              val bl_res = filterInSD(2)(big_left, getSD(big_left))
              val br_res = filterInSD(2)(big_right, getSD(big_right))
              val sl_res = filterInSD(2)(small_left, getSD(small_left))
              val sr_res = filterInSD(2)(small_right, getSD(small_right))
              
              sb.append(s"CHECK_BY = SIZE\n")
              sb.append(s"For BIG_LEFT: ${bl_res.sum / bl_res.length} ms, SD is ${getSD(bl_res)} ms\n")
              sb.append(s"For BIG_RIGHT: ${br_res.sum / br_res.length} ms, SD is ${getSD(br_res)} ms\n")
              sb.append(s"For SMALL_LEFT: ${sl_res.sum / sl_res.length} ms, SD is ${getSD(sl_res)} ms\n")
              sb.append(s"For SMALL_RIGHT: ${sr_res.sum / sr_res.length} ms, SD is ${getSD(sr_res)} ms\n")
              sb.append("\n\n\n")
            } else { }

            val newFile = Paths.get(f.getFileName.toString.replace(".csv", "") + "_final.csv")
            saveTo(newFile) {
              sb.append("ID, SHOW_TIME, ACTION_TIME, SHOW_TIME_MS, DURATION_TIME_MS," +
                "STAND_SIZE, ACTION_SIZE, SIZE_IS_BIG, ACTION_ORDER, STI_IS_LEFT," +
                "CHECK_BY, ANSWER, ORDER_IS_LATE, STAND_ANSWER, CORRECT").append("\n")
              i.foreach(data => {
                import DataConvert._
                sb.append(data.id).append(", ")
                sb.append(data.show_time).append(", ")
                sb.append(data.action_time).append(", ")
                sb.append(data.show_time_ms).append(", ")

                data.duration_time_ms.inSb
                data.stand_size.inSb
                data.action_size.inSb
                data.size_is_big.str.inSb
                data.action_order.inSb
                data.sti_is_left.str.inSb
                data.check_by.inSb
                data.answer.str.inSb
                data.order_is_late.str.inSb
                data.stand_answer.str.inSb
                data.correct.str.endLineInSb
              })
              sb
            }
        })
    })

  def runInJava(isPreExp:Boolean = false): Unit = {
    printToFile(Paths.get("result.log")) {
      invoke(Paths.get("."), isPreExp = isPreExp)
    }
  }
}

/**
  * 数据结构
  */
case class Data(
                 id: String,
                 show_time: Long,
                 action_time: Long,
                 show_time_ms: Long,
                 duration_time_ms: Long,
                 stand_size: Long,
                 action_size: Long,
                 size_is_big: Boolean,
                 action_order: Long,
                 sti_is_left: Boolean,
                 check_by: String,
                 answer: Boolean,
                 var real:Boolean = true,
                 var isPreExp:Boolean = false
               ) {
  /**
    * 对于 ORDER 判断条件的正确答案
    * @return
    */
  def order_is_late: Boolean = {
      if (action_order == 0 || action_order == 1) false
      else if (action_order == 3 || action_order == 4) true
      else throw new RuntimeException(s"ACTION_ORDER 列错误 -> $action_order")
  }

  /**
    * 对于 PRE_EXP 预实验的正确答案
    * @return
    */
  def stand_answer: Boolean = {
    //如果是预实验,那么 奇数返回 false,偶数返回 true
    if (action_order == 0 || action_order == 4) false else true
  }

  /**
    * 根据不同条件的正确答案,结合主试的输入答案,计算出是否正确
    * @return 比较结果
    */
  def correct: Boolean = {
    if (isPreExp) {
      if (stand_answer && answer) true
      else if (!stand_answer && !answer) true
      else false
    } else if (check_by == "SIZE") {
      if (size_is_big && answer) true
      else if (!size_is_big && !answer) true
      else false
    } else if (check_by == "ORDER") {
      if (order_is_late && answer) true
      else if (!order_is_late && !answer) true
      else false
    } else false
  }
}

Version 1.2.6

添加了语音处理类、语音检测使用工具。

修正了 DataUtils getSD 计算时使用的是总体数据,而非样本数据的问题。

2019-03-11 Corkine Ma @ CCNU

Version 1.2.5

添加了数据处理的 DataUtils 工具类和 DataConvert 数据转换类(均使用 Scala 实现,不提供 Java API,虽然也可以使用 —— 因为使用了一些 Scala 的特殊语法来简化开发,比如隐式类型转换、柯里化、表达式即值等特性)。

此工具类可以用于快速的从一堆文件中,对每个文件直接读取到某种数据结构,然后对数据集合进行处理,之后打印到某个流中。

只用提供数据结构,然后定义从文件的每行向数据结构的转换方法即可。此外,可以在转换之前,在读取之后,进行一些统计性的工作。当转换完成 —— 转换为数据集合后,可以对数据进行各种类型的操作,程序

提供了计算标准差、过滤 N 个标准差的简便方法。此外,也提供了用于快速遍历某个文件夹的快速函数,提供了快速打印输出到文件中保存的快速函数。

2019-03-06 Corkine Ma @ CCNU

Version 1.2.4

在当前的版本中,使用 Scala 混合 Java 实现了 Psy4J。相比较之前的版本,现在版本只用声明一个文件即可,请参照 com.mazhangjing.demo/experiment 中的例子,创建一个 Experiment 的实现,然后使用 LabUtils 类提供的 Builder 方法创建若干个包裹 Screen 的 Trial,并且将其注入到 Experiment 中,如果需要保存数据,在 Experiment 的方法中提供实现即可。

程序总是运行 Main.java,因此,你需要通过 classpath 下的 invoke.properties 声明需要反射创建的一个叫做 ExpRunner 的实现,这个实现提供了你的实验信息,包括 title、log、experiment 的 className(用于反射创建),以及 EventMaker。

EventMaker 是版本新加入的类,此类实现后,由 ExpRunner 交付给 Main.java,然后后者会通过反射构建 EventMaker 实例,并且将其放入单独的线程中运行。此实例可以通过 Main 注入的 Experiment 实例来操纵 Screen 的 eventHandler 回调方法,提供比如语音检测之类的功能。

2019-02-22 Corkine Ma @ CCNU

Version 1.0.0

一组带有高精度定时器的类在 com.mazhangjing.lab 中实现。使用者可以参照 com.mazhangjing.zsw 中的结构层次,继承 Screen、Trial、Experiment,然后将其拼装在一起即可。参照 Main.java 中的内容,可以驱动你的 Experiment 运行。

程序并未提供实验刺激的注入方法,没有提供 CSS 默认样式,没有提供日志的记录框架,这保证了程序设计的灵活性,同时隐藏了最为复杂,同时抽象复用率最高的部分 —— 序列调用、定时器自动跳转、阻断跳转、自定义动作和反应。

你需要在 Screen 实现的 initScreen 中提供 duration 超时,以及 layout 呈现的静态结构,你可以在此方法中定义各种组件的 Event,比如按钮、点击、移动等等。Screen 可以由 Trial 的 screens 容器管理,后者在 initTrials 方法中设置,而 Trial 序列由 Experiment 的 trials 属性和 initExperiment 方法管理。在此处注入你的刺激,组建你的 Trial 和 Screen。

Screen 默认注入了 Experiment 和 Scene 对象,这可以方便你预先定义结构的时候,利用这些运行时对象提供独特的行为。除了 initScreen 方法,eventHandler 方法提供了鼠标移入、按键按下、鼠标点击的全局事件注册,你可以根据性能和需要在 Main.java 中处理这部分事件的注册。在这里可以方便的对全局按键进行检测,同时对用户鼠标移动做出反应,比如,取消定时,观察用户的动作。

所有的静态变量存储在枚举 SET.java 中,所有的刺激生成逻辑在 StiFactory.java 中,这些部分由你自己实现。com.mazhangjing.zsw 包中提供了一些实现的示例。

如果你需要保存一些关键信息,可以使用 Experiment 的 setData 或者 setGlobalData 方法,前者保存在 Trial 中,后者保存在 Experiment 中,程序默认在退出界面后调用 saveData 方法进行数据保存,你可以在这里处理数据的序列化。com.mazhangjing.zsw 中提供了一种基于 Log4J 的实现。这部分也由你自己定义。

2018-12-24 Corkine Ma @ CCNU

You can’t perform that action at this time.