替换菜品识别模型,暂时取消相似的判断,查询全部数据测试新模型效果,暂时取消查询前后数据量不一致的判断避免查不出数据

This commit is contained in:
mazengfei
2026-02-11 18:05:40 +08:00
parent 655e9cd56e
commit f562abc12f
9 changed files with 274 additions and 125 deletions
Binary file not shown.
+199 -76
View File
@@ -1,113 +1,236 @@
{
"class_names": [
"三黄鸡",
"上海青WP2026020200041",
"冰草WP2026020900054",
"刀豆",
"北豆腐WP2025111900007",
"味精WP2026013000034",
"圆生菜WP2026020900053",
"圆白菜[卷心菜,包菜]WP2025111000063",
"基围虾WP2025110700019",
"多宝鱼WP2025112600011",
"大白菜WP2025111000064",
"大葱WP2025111000060",
"大葱WP2026013000025",
"姜[黄姜]WP2025112000071",
"娃娃菜WP2025120300018",
"娃娃菜WP2026020200005",
"小米辣椒WP2026013000027",
"小葱WP2026013000029",
"山药(带皮)WP2026020200008",
"巨人笋",
"广东菜心WP2025120100014",
"松花蛋",
"松茸菌WP2025120100016",
"沙葱",
"洋葱(鲜)[葱头]WP2025111000061",
"牛腩WP2025110700043",
"猪五花肉WP2026020300048",
"猪腿肉WP2025110700040",
"玉米WP2025120100017",
"番茄WP2025110700016",
"白吉饼WP2026013000024",
"笋",
"粉丝",
"紫甘蓝",
"红提子WP2025111900006",
"红椒",
"红美人椒WP2026020300049",
"红薯",
"红薯粉条(袋)",
"羊排WP2025120300021",
"羊血WP2026013000038",
"胡萝卜WP2025110600008",
"芋头",
"花卷",
"芹菜WP2025111000065",
"苦菊",
"茄子(紫皮,长)WP2025110700024",
"茼蒿WP2025111900005",
"莴笋(鲜)[莴苣]WP2025111000056",
"茼蒿WP2026020200045",
"莲藕(带皮)",
"莴笋(鲜)[莴苣]WP2026020200009",
"菜花(白色)[花椰菜]",
"蒜WP2025111400003",
"蒜苗WP2026013000028",
"蒸鱼豉油WP2026013000032",
"西兰花[绿菜花]WP2025110700029",
"西葫芦",
"豆腐皮WP2025111000059",
"豆角",
"辣椒(红,小)WP2025111000066",
"辣椒(青辣椒,螺丝椒)WP2025110700017",
"醪糟",
"金针菇WP2025111900010",
"马铃薯[土豆、洋芋]WP2025110600006",
"青线椒WP2026013000031",
"韭黄",
"香菇",
"香菜WP2026013000030",
"马铃薯[土豆、洋芋]WP2026012900001",
"鸡全腿(带骨)",
"鸡翅(带骨)",
"鸡腿肉WP2025110700048",
"鸡蛋(代表值)WP2025112000070",
"鸡腿(带骨)",
"鸡蛋(代表值)WP2026013000039",
"麦芹",
"黄瓜(鲜)[胡瓜]WP2025110700031",
"黄豆芽WP2026020200042",
"黑袋子",
"黑鱼(整条鱼)WP2025110600012"
],
"class_to_idx": {
"北豆腐WP2025111900007": 0,
"圆白菜[卷心菜,包菜]WP2025111000063": 1,
"基围虾WP2025110700019": 2,
"多宝鱼WP2025112600011": 3,
"大白菜WP2025111000064": 4,
"大葱WP2025111000060": 5,
"姜[黄姜]WP2025112000071": 6,
"娃娃菜WP2025120300018": 7,
"广东菜心WP2025120100014": 8,
"松茸菌WP2025120100016": 9,
"洋葱(鲜)[葱头]WP2025111000061": 10,
"牛腩WP2025110700043": 11,
"猪腿肉WP2025110700040": 12,
"玉米WP2025120100017": 13,
"番茄WP2025110700016": 14,
"红提子WP2025111900006": 15,
"羊排WP2025120300021": 16,
"胡萝卜WP2025110600008": 17,
"芹菜WP2025111000065": 18,
"茄子(紫皮,长)WP2025110700024": 19,
"茼蒿WP2025111900005": 20,
"莴笋(鲜)[莴苣]WP2025111000056": 21,
"WP2025111400003": 22,
"西兰花[绿菜花]WP2025110700029": 23,
"豆腐皮WP2025111000059": 24,
"辣椒(红,小)WP2025111000066": 25,
"辣椒(青辣椒,螺丝椒)WP2025110700017": 26,
"金针菇WP2025111900010": 27,
"马铃薯[土豆、洋芋]WP2025110600006": 28,
"鸡腿肉WP2025110700048": 29,
"鸡蛋(代表值)WP2025112000070": 30,
"黄瓜(鲜)[胡瓜]WP2025110700031": 31,
"黑袋子": 32,
"黑鱼(整条鱼)WP2025110600012": 33
"三黄鸡": 0,
"上海青WP2026020200041": 1,
"冰草WP2026020900054": 2,
"刀豆": 3,
"北豆腐WP2025111900007": 4,
"味精WP2026013000034": 5,
"圆生菜WP2026020900053": 6,
"圆白菜[卷心菜,包菜]WP2025111000063": 7,
"基围虾WP2025110700019": 8,
"多宝鱼WP2025112600011": 9,
"大白菜WP2025111000064": 10,
"大葱WP2026013000025": 11,
"姜[黄姜]WP2025112000071": 12,
"娃娃菜WP2026020200005": 13,
"小米辣椒WP2026013000027": 14,
"小葱WP2026013000029": 15,
"山药(带皮)WP2026020200008": 16,
"巨人笋": 17,
"广东菜心WP2025120100014": 18,
"松花蛋": 19,
"松茸菌WP2025120100016": 20,
"沙葱": 21,
"洋葱(鲜)[葱头]WP2025111000061": 22,
"牛腩WP2025110700043": 23,
"猪五花肉WP2026020300048": 24,
"猪腿肉WP2025110700040": 25,
"玉米WP2025120100017": 26,
"番茄WP2025110700016": 27,
"白吉饼WP2026013000024": 28,
"": 29,
"粉丝": 30,
"紫甘蓝": 31,
"红提子WP2025111900006": 32,
"红椒": 33,
"红美人椒WP2026020300049": 34,
"红薯": 35,
"红薯粉条(袋)": 36,
"羊排WP2025120300021": 37,
"羊血WP2026013000038": 38,
"胡萝卜WP2025110600008": 39,
"芋头": 40,
"花卷": 41,
"芹菜WP2025111000065": 42,
"苦菊": 43,
"茄子(紫皮,长)WP2025110700024": 44,
"茼蒿WP2026020200045": 45,
"莲藕(带皮)": 46,
"莴笋(鲜)[莴苣]WP2026020200009": 47,
"菜花(白色)[花椰菜]": 48,
"蒜WP2025111400003": 49,
"蒜苗WP2026013000028": 50,
"蒸鱼豉油WP2026013000032": 51,
"西兰花[绿菜花]WP2025110700029": 52,
"西葫芦": 53,
"豆腐皮WP2025111000059": 54,
"豆角": 55,
"辣椒(红,小)WP2025111000066": 56,
"辣椒(青辣椒,螺丝椒)WP2025110700017": 57,
"醪糟": 58,
"金针菇WP2025111900010": 59,
"青线椒WP2026013000031": 60,
"韭黄": 61,
"香菇": 62,
"香菜WP2026013000030": 63,
"马铃薯[土豆、洋芋]WP2026012900001": 64,
"鸡全腿(带骨)": 65,
"鸡翅(带骨)": 66,
"鸡腿肉WP2025110700048": 67,
"鸡腿(带骨)": 68,
"鸡蛋(代表值)WP2026013000039": 69,
"麦芹": 70,
"黄瓜(鲜)[胡瓜]WP2025110700031": 71,
"黄豆芽WP2026020200042": 72,
"黑袋子": 73,
"黑鱼(整条鱼)WP2025110600012": 74
},
"idx_to_class": {
"0": "北豆腐WP2025111900007",
"1": "圆白菜[卷心菜,包菜]WP2025111000063",
"2": "基围虾WP2025110700019",
"3": "多宝鱼WP2025112600011",
"4": "大白菜WP2025111000064",
"5": "大葱WP2025111000060",
"6": "姜[黄姜]WP2025112000071",
"7": "娃娃菜WP2025120300018",
"8": "广东菜心WP2025120100014",
"9": "松茸菌WP2025120100016",
"10": "洋葱(鲜)[葱头]WP2025111000061",
"11": "牛腩WP2025110700043",
"12": "猪腿肉WP2025110700040",
"13": "玉米WP2025120100017",
"14": "番茄WP2025110700016",
"15": "红提子WP2025111900006",
"16": "羊排WP2025120300021",
"17": "胡萝卜WP2025110600008",
"18": "芹菜WP2025111000065",
"19": "茄子(紫皮,长)WP2025110700024",
"20": "茼蒿WP2025111900005",
"21": "莴笋(鲜)[莴苣]WP2025111000056",
"22": "WP2025111400003",
"23": "西兰花[绿菜花]WP2025110700029",
"24": "豆腐皮WP2025111000059",
"25": "辣椒(红,小)WP2025111000066",
"26": "辣椒(青辣椒,螺丝椒)WP2025110700017",
"27": "金针菇WP2025111900010",
"28": "马铃薯[土豆、洋芋]WP2025110600006",
"29": "鸡腿肉WP2025110700048",
"30": "鸡蛋(代表值)WP2025112000070",
"31": "黄瓜(鲜)[胡瓜]WP2025110700031",
"32": "黑袋子",
"33": "黑鱼(整条鱼)WP2025110600012"
"0": "三黄鸡",
"1": "上海青WP2026020200041",
"2": "冰草WP2026020900054",
"3": "刀豆",
"4": "北豆腐WP2025111900007",
"5": "味精WP2026013000034",
"6": "圆生菜WP2026020900053",
"7": "圆白菜[卷心菜,包菜]WP2025111000063",
"8": "基围虾WP2025110700019",
"9": "多宝鱼WP2025112600011",
"10": "大白菜WP2025111000064",
"11": "大葱WP2026013000025",
"12": "姜[黄姜]WP2025112000071",
"13": "娃娃菜WP2026020200005",
"14": "小米辣椒WP2026013000027",
"15": "小葱WP2026013000029",
"16": "山药(带皮)WP2026020200008",
"17": "巨人笋",
"18": "广东菜心WP2025120100014",
"19": "松花蛋",
"20": "松茸菌WP2025120100016",
"21": "沙葱",
"22": "洋葱(鲜)[葱头]WP2025111000061",
"23": "牛腩WP2025110700043",
"24": "猪五花肉WP2026020300048",
"25": "猪腿肉WP2025110700040",
"26": "玉米WP2025120100017",
"27": "番茄WP2025110700016",
"28": "白吉饼WP2026013000024",
"29": "",
"30": "粉丝",
"31": "紫甘蓝",
"32": "红提子WP2025111900006",
"33": "红椒",
"34": "红美人椒WP2026020300049",
"35": "红薯",
"36": "红薯粉条(袋)",
"37": "羊排WP2025120300021",
"38": "羊血WP2026013000038",
"39": "胡萝卜WP2025110600008",
"40": "芋头",
"41": "花卷",
"42": "芹菜WP2025111000065",
"43": "苦菊",
"44": "茄子(紫皮,长)WP2025110700024",
"45": "茼蒿WP2026020200045",
"46": "莲藕(带皮)",
"47": "莴笋(鲜)[莴苣]WP2026020200009",
"48": "菜花(白色)[花椰菜]",
"49": "蒜WP2025111400003",
"50": "蒜苗WP2026013000028",
"51": "蒸鱼豉油WP2026013000032",
"52": "西兰花[绿菜花]WP2025110700029",
"53": "西葫芦",
"54": "豆腐皮WP2025111000059",
"55": "豆角",
"56": "辣椒(红,小)WP2025111000066",
"57": "辣椒(青辣椒,螺丝椒)WP2025110700017",
"58": "醪糟",
"59": "金针菇WP2025111900010",
"60": "青线椒WP2026013000031",
"61": "韭黄",
"62": "香菇",
"63": "香菜WP2026013000030",
"64": "马铃薯[土豆、洋芋]WP2026012900001",
"65": "鸡全腿(带骨)",
"66": "鸡翅(带骨)",
"67": "鸡腿肉WP2025110700048",
"68": "鸡腿(带骨)",
"69": "鸡蛋(代表值)WP2026013000039",
"70": "麦芹",
"71": "黄瓜(鲜)[胡瓜]WP2025110700031",
"72": "黄豆芽WP2026020200042",
"73": "黑袋子",
"74": "黑鱼(整条鱼)WP2025110600012"
},
"embedding_dim": 512,
"total_images": 2065,
"build_time": "2025-12-05T10:16:42.915631"
"total_images": 4254,
"build_time": "2026-02-10T08:08:53.308245"
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -607,6 +607,7 @@ class FoodCollectionActivity : ComponentActivity() {
tip
mDialogWaiting = CustomDialog(this, view, R.style.MyDialog)
mDialogWaiting!!.show()
mDialogWaiting!!.setCanceledOnTouchOutside(true)
mDialogWaiting!!.setCancelable(true)
}
}
@@ -628,6 +629,7 @@ class FoodCollectionActivity : ComponentActivity() {
val view = View.inflate(this, R.layout.dialog_waiting, null)
mDialogWaiting = CustomDialog(this, view, R.style.MyDialog)
mDialogWaiting!!.show()
mDialogWaiting!!.setCanceledOnTouchOutside(true)
mDialogWaiting!!.setCancelable(true)
}
val contentView = mDialogWaiting?.findViewById<ViewGroup>(android.R.id.content)
@@ -377,7 +377,8 @@ abstract class GoodsListActivity : ComponentActivity() {
if (newBmp.isRecycled.not()) {
newBmp.recycle()
}
val count = foodList.count { it.score < 0.15 }
// val count = foodList.count { it.score < 0.15 }
val count = foodList.size
if (count == 0) {
//未识别到满足相似度>=0.85的,显示识别率最高的
var tempList = foodList.filter { it.name.contains("黑袋子").not() }
@@ -429,7 +430,9 @@ abstract class GoodsListActivity : ComponentActivity() {
return@runOnUiThread
}
handleGoodsResult(
goodsList = if (goodsList.size == foodList.size) goodsList else mutableListOf(),
// TODO: 暂时取消不一致判断,显示查询到的菜品
// goodsList = if (goodsList.size == foodList.size) goodsList else mutableListOf(),
goodsList = goodsList,
// file = null,
// name = foodList.joinToString(",")
)
@@ -16,9 +16,9 @@ class RequestInterceptor : Interceptor {
val requestBuilder = originalRequest.newBuilder()
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("deviceId", GlobalData.deviceId)
.header("X-DEVICE-CODE", GlobalData.deviceId)
// .header("X-DEVICE-CODE", "bcf396ed-78f6-3864-9837-7c37c5b2ec41")
.header("deviceId", if (GlobalData.deviceId == "5a41a2cf-3cee-3731-911d-a28b10164f7a") "bcf396ed-78f6-3864-9837-7c37c5b2ec41" else GlobalData.deviceId)
// .header("X-DEVICE-CODE", GlobalData.deviceId)
.header("X-DEVICE-CODE", if (GlobalData.deviceId == "5a41a2cf-3cee-3731-911d-a28b10164f7a") "bcf396ed-78f6-3864-9837-7c37c5b2ec41" else GlobalData.deviceId)
.header("x-access-token", GlobalData.appToken)
.header("authorization", "57ee87183f2a4fa59683ec9ef41c8f5d")
// .header("Authorization", "Bearer ${getToken()}")
@@ -4,10 +4,12 @@ import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.renderscript.Element.DataType
import android.util.Log
import androidx.core.graphics.scale
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.sw.inbound.MyApp
import com.sw.inbound.objbox.ObjectBox.boxStore
import com.sw.inbound.utils.AssetsTool
import com.sw.inbound.utils.ImageUtil
import com.sw.inbound.utils.ext.toJsonString
@@ -16,6 +18,7 @@ import io.objectbox.kotlin.boxFor
import io.objectbox.query.IdWithScore
import io.objectbox.query.Query
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import okhttp3.internal.closeQuietly
import org.pytorch.IValue
@@ -31,12 +34,14 @@ import java.io.InputStream
object FoodModule {
private lateinit var module_mobile: Module
// private lateinit var box: Box<Food>
// private lateinit var box: Box<Food>
private lateinit var embeddingsList: List<List<Float>>
private lateinit var labelsList: IntArray
private lateinit var classInfo: FoodClassInfo
private val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f)
private val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f)
// 1. 定义你的模型固定输入尺寸 (根据你的tflite模型修改,比如224x224)
private const val MODEL_INPUT_WIDTH = 300
private const val MODEL_INPUT_HEIGHT = 300
@@ -47,12 +52,7 @@ object FoodModule {
suspend fun init(context: Context) {
withContext(Dispatchers.IO) {
module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt"))
ObjectBox.safeDbOp {
val box = ObjectBox.getBox<Food>()
if (box?.all?.isEmpty() == true) {
initDefFoodData(context)
}
}
initDefFoodData(context)
}
}
@@ -65,13 +65,13 @@ object FoodModule {
// }
fun bitmap2FloatArray(originBitmap: Bitmap, isRecycle: Boolean): FloatArray? {
var rgb565Bitmap: Bitmap?=null
var rgb565Bitmap: Bitmap? = null
try {
val scaledBitmap = originBitmap.scale(MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT)
if (isRecycle) {
originBitmap.recycle()
}
rgb565Bitmap = scaledBitmap.copy(Bitmap.Config.RGB_565, false)
rgb565Bitmap = scaledBitmap.copy(Bitmap.Config.RGB_565, false)
scaledBitmap.recycle()
val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
rgb565Bitmap,
@@ -114,7 +114,10 @@ object FoodModule {
// return queryFoodNameScore(floatArray, queryCount)
// }
suspend fun queryFoodNameScore(floatArray: FloatArray?, queryCount: Int = 15): List<IdNameScore> {
suspend fun queryFoodNameScore(
floatArray: FloatArray?,
queryCount: Int = 15
): List<IdNameScore> {
if (floatArray == null) return emptyList()
// val query: Query<Food> = box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build()
// //查询比较分数
@@ -126,15 +129,18 @@ object FoodModule {
// // 先关闭Query,释放Cursor
// query.close()
// }
Timber.tag("FoodModule").d("queryFood向量:${floatArray.slice(0 until 50).toJsonString()}")
val idScoreList = ObjectBox.query(floatArray, queryCount)
val nameScoreList = mutableListOf<IdNameScore>()
idScoreList.forEach {
val name = ObjectBox.get(it.id)?.name?:""
val name = ObjectBox.get(it.id)?.name ?: ""
nameScoreList.add(IdNameScore(id = it.id, name = name, score = it.score))
}
Timber.tag("FoodModule").d("queryFood数据:${nameScoreList.toJsonString()}")
return nameScoreList
}
suspend fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
val floatArray = bitmap2FloatArray(bitmap, false) ?: return emptyList()
Timber.tag("FoodModule").d("向量:${floatArray.toJsonString()}")
@@ -165,6 +171,7 @@ object FoodModule {
Timber.tag("FoodModule").d("getFoodScoreList数据:${sortedScoreList.toJsonString()}")
return sortedScoreList
}
suspend fun queryFood(floatArray: FloatArray, queryCount: Int = 15): List<String> {
val map = mutableMapOf<String, Int>()
val nameScoreList = queryFoodNameScore(floatArray, queryCount)
@@ -177,39 +184,51 @@ object FoodModule {
}
data class IdNameScore(
val id:Long,
var name:String,
val id: Long,
var name: String,
val score: Double
)
suspend fun initDefFoodData(context: Context, action:()-> Unit={}) {
val count = ObjectBox.getBox<Food>()?.all?.count { it.foodIdx == DEFAULT_FOOD_INDEX }?:0
if (count > 0) {
return
}
val embeddingsJson = AssetsTool.readAssetsFile(context, "data/embeddings.json")
val labelsJson = AssetsTool.readAssetsFile(context, "data/labels.json")
val classInfoJson = AssetsTool.readAssetsFile(context, "data/class_info.json")
suspend fun initDefFoodData(context: Context, action: () -> Unit = {}) {
try {
val count =
ObjectBox.getBox<Food>()?.all?.count { it.foodIdx == DEFAULT_FOOD_INDEX } ?: 0
if (count > 0) {
return
}
val embeddingsJson = AssetsTool.readAssetsFile(context, "data/embeddings.json")
val labelsJson = AssetsTool.readAssetsFile(context, "data/labels.json")
val classInfoJson = AssetsTool.readAssetsFile(context, "data/class_info.json")
embeddingsList =
Gson().fromJson(embeddingsJson, object : TypeToken<List<List<Float>>>() {}.type)
labelsList = Gson().fromJson(labelsJson, IntArray::class.java)
classInfo =
Gson().fromJson(classInfoJson, FoodClassInfo::class.java)
embeddingsList =
Gson().fromJson(embeddingsJson, object : TypeToken<List<List<Float>>>() {}.type)
labelsList = Gson().fromJson(labelsJson, IntArray::class.java)
classInfo =
Gson().fromJson(classInfoJson, FoodClassInfo::class.java)
val foodMap = classInfo.idx_to_class
val list = mutableListOf<Food>()
embeddingsList.forEachIndexed { index, floatList ->
val classIdx = labelsList[index]
val foodName = foodMap["$classIdx"]
val array = floatList.toFloatArray()
// ObjectBox.boxStore.runInTx {
// box.put(Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX))
// }
list.add(Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX))
val foodMap = classInfo.idx_to_class
val list = mutableListOf<Food>()
val size = embeddingsList.size
val size2 = labelsList.size
val size3 = foodMap.size
Log.d("TAG", "initDefFoodData: $size,$size2,$size3")
embeddingsList.forEachIndexed { index, floatList ->
val classIdx = labelsList[index]
val foodName = foodMap["$classIdx"]
val array = floatList.toFloatArray()
// ObjectBox.boxStore.runInTx {
// box.put(Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX))
// }
val food = Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX)
list.add(food)
}
// val tempList = list.chunked(10)
ObjectBox.putAll(list)
action()
} catch (e: Exception) {
e.printStackTrace()
Log.d("TAG", "initDefFoodData: ---${e.message}--")
}
ObjectBox.putAll(list)
action()
}
@@ -140,6 +140,7 @@ object ObjectBox {
dbMutex.withLock { operation() }
} catch (e: FileCorruptException) {
// 操作中触发损坏,尝试重建数据库
e.printStackTrace()
//val context = boxStore.context
//deleteDbFiles(context)
init(MyApp.instance!!)
@@ -173,19 +174,20 @@ object ObjectBox {
}
suspend fun putAll(entities: List<Food>) = safeDbOp {
getBox<Food>()?.put(entities)
val box = getBox<Food>()
box?.put(entities)
}
suspend fun filter(name:String?) = safeDbOp {
suspend fun filter(name: String?) = safeDbOp {
val list = getBox<Food>()?.all?.distinctBy { it.name }
if (name.isNullOrBlank().not()) {
list?.filter { it.name?.contains(name) == true }
} else {
list
}?:emptyList()
} ?: emptyList()
}
suspend fun remove(name:String) = safeDbOp {
suspend fun remove(name: String) = safeDbOp {
getBox<Food>()?.run {
val filterIdList = all.filter { it.name == name }.map { it.id }
removeByIds(filterIdList)