From da461b2e3ec4026f1e8805a7d3ac56ceb46e38f5 Mon Sep 17 00:00:00 2001 From: sanaxr0001-tech Date: Sat, 3 Oct 2026 12:51:05 +0100 Subject: [PATCH 1/4] Add NEST 96 well flat-bottom cell culture plate, 701011 (nest_96_wellplate_200uL_Fb) --- .../img/nest/nest_96_wellplate_200uL_Fb.webp | Bin 0 -> 29178 bytes docs/resources/library/nest.md | 3 +- pylabrobot/resources/nest/plates.py | 35 +++++++++++++ pylabrobot/resources/nest/plates_tests.py | 49 ++++++++++++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 docs/resources/library/img/nest/nest_96_wellplate_200uL_Fb.webp create mode 100644 pylabrobot/resources/nest/plates_tests.py diff --git a/docs/resources/library/img/nest/nest_96_wellplate_200uL_Fb.webp b/docs/resources/library/img/nest/nest_96_wellplate_200uL_Fb.webp new file mode 100644 index 0000000000000000000000000000000000000000..5e52cd007aec42aa0634d17e156f6342f8600ba4 GIT binary patch literal 29178 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 V=u>%llsC0B(g=sSVf4IiKmcsy3RM6A literal 0 HcmV?d00001 diff --git a/docs/resources/library/nest.md b/docs/resources/library/nest.md index 19ee44fdf53..c8f69c866a0 100644 --- a/docs/resources/library/nest.md +++ b/docs/resources/library/nest.md @@ -10,4 +10,5 @@ Wuxi NEST Biotechnology Co., Ltd. a leading life science plastic consumables man | 'nest_1_troughplate_185000uL_Vb'
Part no.: 360101
[manufacturer website](https://www.nest-biotech.com/reagent-reserviors/59178415.html)
- Material: polypropylene | ![](img/nest/nest_1_troughplate_185000uL_Vb.webp) | `nest_1_troughplate_185000uL_Vb` | | 'nest_8_troughplate_22000uL_Vb'
Part no.: 360101
[manufacturer website](https://www.nestscientificusa.com/product/detail/513006470820794368)
- Material: polypropylene | ![](img/nest/nest_8_troughplate_22000uL_Vb.jpg) | `nest_8_troughplate_22000uL_Vb` | | 'nest_12_troughplate_15000uL_Vb'
Part no.: 360102
[manufacturer website](https://www.nestscientificusa.com/product/detail/513006470820794368)
- Material: polypropylene | ![](img/nest/nest_12_troughplate_15000uL_Vb.jpg) | `nest_12_troughplate_15000uL_Vb` | -| 'NEST_96_wellplate_2200uL_Ub'
Part no.: 503062
[manufacturer website](https://www.nest-biotech.com/deep-well-plates/59253727.html)
- Material: polypropylene | ![](img/nest/NEST_96_wellplate_2200uL_Ub.png) | `NEST_96_wellplate_2200uL_Ub` | \ No newline at end of file +| 'NEST_96_wellplate_2200uL_Ub'
Part no.: 503062
[manufacturer website](https://www.nest-biotech.com/deep-well-plates/59253727.html)
- Material: polypropylene | ![](img/nest/NEST_96_wellplate_2200uL_Ub.png) | `NEST_96_wellplate_2200uL_Ub` | +| 'nest_96_wellplate_200uL_Fb'
Part no.: 701011
[manufacturer website](https://www.nest-biotech.com/cell-culture-plates/59415537.html)
- Material: polystyrene
- Non-treated, sterile | ![](img/nest/nest_96_wellplate_200uL_Fb.webp) | `nest_96_wellplate_200uL_Fb` | diff --git a/pylabrobot/resources/nest/plates.py b/pylabrobot/resources/nest/plates.py index 7ae66387ff8..f5661529073 100644 --- a/pylabrobot/resources/nest/plates.py +++ b/pylabrobot/resources/nest/plates.py @@ -225,3 +225,38 @@ def NEST_96_wellplate_2200uL_Ub(name: str) -> Plate: name_prefix=name, ), ) + + +def nest_96_wellplate_200uL_Fb(name: str) -> Plate: + """NEST Cat. No. 701011: 96 well cell culture plate, flat bottom, non-treated, polystyrene. + Spec: NEST data sheet, "96 well cell culture plate, Flat bottom" technical drawing + http://www.gzjetway.com/upload/pdf/202506/04a71256d0b04cf888df963be987194b.pdf + Recommended medium volume: 0.1-0.2 mL, growth area: 0.32 cm2 per well + https://www.nest-biotech.com/cell-culture-plates/59415537.html + """ + return Plate( + name=name, + size_x=127.56, # from spec + size_y=85.36, # from spec + size_z=14.30, # from spec + lid=None, + model=nest_96_wellplate_200uL_Fb.__name__, + ordered_items=create_ordered_items_2d( + Well, + size_x=6.85, # from spec (inner diameter at the rim) + size_y=6.85, # from spec (inner diameter at the rim) + size_z=12.00, # from spec (14.30 - 2.30) + dx=10.855, # from spec (A1 center 14.28 - 6.85 / 2) + dy=7.755, # from spec (row H center 11.18 from the front edge - 6.85 / 2) + dz=2.30, # from spec (3.50 cavity floor - 1.20 floor thickness) + material_z_thickness=1.20, # from spec + item_dx=9.0, # from spec + item_dy=9.0, # from spec + num_items_x=12, # from spec + num_items_y=8, # from spec + cross_section_type=CrossSectionType.CIRCLE, + bottom_type=WellBottomType.FLAT, + max_volume=200, # from spec (upper end of the recommended medium volume, 0.1-0.2 mL) + name_prefix=name, + ), + ) diff --git a/pylabrobot/resources/nest/plates_tests.py b/pylabrobot/resources/nest/plates_tests.py new file mode 100644 index 00000000000..f3a394f0546 --- /dev/null +++ b/pylabrobot/resources/nest/plates_tests.py @@ -0,0 +1,49 @@ +import unittest + +from pylabrobot.resources import Coordinate, Plate, nest_96_wellplate_200uL_Fb +from pylabrobot.resources.well import CrossSectionType, WellBottomType + + +class TestNest96Wellplate200uLFb(unittest.TestCase): + def setUp(self): + self.plate = nest_96_wellplate_200uL_Fb(name="plate") + self.plate.location = Coordinate.zero() + + def test_footprint(self): + self.assertEqual(self.plate.get_size_x(), 127.56) + self.assertEqual(self.plate.get_size_y(), 85.36) + self.assertEqual(self.plate.get_size_z(), 14.30) + + def test_layout(self): + self.assertEqual(self.plate.num_items_x, 12) + self.assertEqual(self.plate.num_items_y, 8) + self.assertEqual(len(self.plate.get_all_items()), 96) + + def test_well_centers(self): + # The data sheet places the center of A1 14.28 mm from the left and 11.18 mm from the back edge, + # at a pitch of 9.00 mm; H12 is 99.00 mm to the right of A1 and 63.00 mm in front of it. + a1 = self.plate.get_well("A1").get_absolute_location("c", "c", "b") + self.assertAlmostEqual(a1.x, 14.28) + self.assertAlmostEqual(a1.y, 85.36 - 11.18) + h12 = self.plate.get_well("H12").get_absolute_location("c", "c", "b") + self.assertAlmostEqual(h12.x, 14.28 + 99.00) + self.assertAlmostEqual(h12.y, 85.36 - 11.18 - 63.00) + + def test_well_geometry(self): + well = self.plate.get_well("A1") + self.assertEqual(well.get_size_x(), 6.85) + self.assertEqual(well.get_size_y(), 6.85) + self.assertEqual(well.bottom_type, WellBottomType.FLAT) + self.assertEqual(well.cross_section_type, CrossSectionType.CIRCLE) + self.assertEqual(well.max_volume, 200) + + # The cavity is 10.80 mm deep: the rim is at the plate height and the floor is 1.20 mm thick. + rim = well.get_absolute_location("c", "c", "t").z + cavity_bottom = well.get_absolute_location().z + well.material_z_thickness + self.assertAlmostEqual(rim, 14.30) + self.assertAlmostEqual(cavity_bottom, 3.50) + self.assertAlmostEqual(rim - cavity_bottom, 10.80) + + def test_serialize_round_trip(self): + plate = nest_96_wellplate_200uL_Fb(name="another_plate") + self.assertEqual(Plate.deserialize(plate.serialize()), plate) From 12068cf0c4e523dc095d193719fc7b21633056f4 Mon Sep 17 00:00:00 2001 From: sanaxr0001-tech Date: Sun, 4 Oct 2026 14:07:01 +0100 Subject: [PATCH 2/4] nest_96_wellplate_200uL_Fb: add height_volume_data Liquid height over the cavity bottom (mm) -> volume (uL) for the tapered well, computed as a truncated cone from the NEST data-sheet drawing (6.40 mm across the floor, 6.85 mm across the rim, 10.80 mm deep). Points every 1.0 mm stay within 0.1 uL of the cone formula; the last point is the brim-full volume, 372.4 uL at 10.80 mm. --- pylabrobot/resources/nest/plates.py | 20 ++++++++++++++++++++ pylabrobot/resources/nest/plates_tests.py | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/pylabrobot/resources/nest/plates.py b/pylabrobot/resources/nest/plates.py index f5661529073..4e03df35f3b 100644 --- a/pylabrobot/resources/nest/plates.py +++ b/pylabrobot/resources/nest/plates.py @@ -227,6 +227,25 @@ def NEST_96_wellplate_2200uL_Ub(name: str) -> Plate: ) +# Liquid height over the cavity bottom (mm) -> volume (uL), computed from spec: the well is a +# truncated cone, 6.40 mm across the floor and 6.85 mm across the rim, 10.80 mm deep. The last +# point is the brim-full volume. +_nest_96_wellplate_200uL_Fb_height_volume_data = { + 0.0: 0.0, + 1.0: 32.4, + 2.0: 65.2, + 3.0: 98.4, + 4.0: 132.1, + 5.0: 166.1, + 6.0: 200.7, + 7.0: 235.6, + 8.0: 271.0, + 9.0: 306.8, + 10.0: 343.1, + 10.8: 372.4, +} + + def nest_96_wellplate_200uL_Fb(name: str) -> Plate: """NEST Cat. No. 701011: 96 well cell culture plate, flat bottom, non-treated, polystyrene. Spec: NEST data sheet, "96 well cell culture plate, Flat bottom" technical drawing @@ -257,6 +276,7 @@ def nest_96_wellplate_200uL_Fb(name: str) -> Plate: cross_section_type=CrossSectionType.CIRCLE, bottom_type=WellBottomType.FLAT, max_volume=200, # from spec (upper end of the recommended medium volume, 0.1-0.2 mL) + height_volume_data=_nest_96_wellplate_200uL_Fb_height_volume_data, name_prefix=name, ), ) diff --git a/pylabrobot/resources/nest/plates_tests.py b/pylabrobot/resources/nest/plates_tests.py index f3a394f0546..8d988bdecf7 100644 --- a/pylabrobot/resources/nest/plates_tests.py +++ b/pylabrobot/resources/nest/plates_tests.py @@ -1,3 +1,4 @@ +import math import unittest from pylabrobot.resources import Coordinate, Plate, nest_96_wellplate_200uL_Fb @@ -44,6 +45,25 @@ def test_well_geometry(self): self.assertAlmostEqual(cavity_bottom, 3.50) self.assertAlmostEqual(rim - cavity_bottom, 10.80) + def test_height_volume_data(self): + well = self.plate.get_well("A1") + cavity_depth = well.get_size_z() - well.material_z_thickness + self.assertEqual(well.compute_volume_from_height(0.0), 0.0) + assert well.height_volume_data is not None + self.assertAlmostEqual(max(well.height_volume_data), cavity_depth) + self.assertAlmostEqual(well.compute_volume_from_height(cavity_depth), 372.4) + + # The well is a truncated cone, 6.40 mm across the floor and 6.85 mm across the rim. + def cone_volume(h: float) -> float: + r_floor, r_rim = 6.40 / 2, 6.85 / 2 + r = r_floor + (r_rim - r_floor) * h / cavity_depth + return math.pi * h * (r_floor**2 + r_floor * r + r**2) / 3 + + for i in range(109): + h = i / 10 + self.assertAlmostEqual(well.compute_volume_from_height(h), cone_volume(h), delta=0.1) + self.assertAlmostEqual(well.compute_height_from_volume(200), 5.98, places=2) + def test_serialize_round_trip(self): plate = nest_96_wellplate_200uL_Fb(name="another_plate") self.assertEqual(Plate.deserialize(plate.serialize()), plate) From 90c2b9182396f8931653ce8ace0d47e6e04e47af Mon Sep 17 00:00:00 2001 From: sanaxr0001-tech Date: Mon, 5 Oct 2026 10:10:45 +0100 Subject: [PATCH 3/4] nest_96_wellplate_200uL_Fb: max_volume is the brim-full volume max_volume is 372.4 uL, the brim-full volume computed from the data-sheet drawing and the last height_volume_data point. height_volume_data keeps three points: 0.0 mm, 6.0 mm (200.7 uL, about the nominal 200 uL) and 10.8 mm (the brim), since the values are computed, not measured. --- pylabrobot/resources/nest/plates.py | 15 +++------------ pylabrobot/resources/nest/plates_tests.py | 9 ++++----- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/pylabrobot/resources/nest/plates.py b/pylabrobot/resources/nest/plates.py index 4e03df35f3b..4c9d284eb61 100644 --- a/pylabrobot/resources/nest/plates.py +++ b/pylabrobot/resources/nest/plates.py @@ -228,20 +228,11 @@ def NEST_96_wellplate_2200uL_Ub(name: str) -> Plate: # Liquid height over the cavity bottom (mm) -> volume (uL), computed from spec: the well is a -# truncated cone, 6.40 mm across the floor and 6.85 mm across the rim, 10.80 mm deep. The last -# point is the brim-full volume. +# truncated cone, 6.40 mm across the floor and 6.85 mm across the rim, 10.80 mm deep. The points +# are at about the nominal volume (200 uL) and at the brim. _nest_96_wellplate_200uL_Fb_height_volume_data = { 0.0: 0.0, - 1.0: 32.4, - 2.0: 65.2, - 3.0: 98.4, - 4.0: 132.1, - 5.0: 166.1, 6.0: 200.7, - 7.0: 235.6, - 8.0: 271.0, - 9.0: 306.8, - 10.0: 343.1, 10.8: 372.4, } @@ -275,7 +266,7 @@ def nest_96_wellplate_200uL_Fb(name: str) -> Plate: num_items_y=8, # from spec cross_section_type=CrossSectionType.CIRCLE, bottom_type=WellBottomType.FLAT, - max_volume=200, # from spec (upper end of the recommended medium volume, 0.1-0.2 mL) + max_volume=372.4, # computed from spec (brim-full, the last height_volume_data point) height_volume_data=_nest_96_wellplate_200uL_Fb_height_volume_data, name_prefix=name, ), diff --git a/pylabrobot/resources/nest/plates_tests.py b/pylabrobot/resources/nest/plates_tests.py index 8d988bdecf7..5cbbb5de932 100644 --- a/pylabrobot/resources/nest/plates_tests.py +++ b/pylabrobot/resources/nest/plates_tests.py @@ -36,7 +36,7 @@ def test_well_geometry(self): self.assertEqual(well.get_size_y(), 6.85) self.assertEqual(well.bottom_type, WellBottomType.FLAT) self.assertEqual(well.cross_section_type, CrossSectionType.CIRCLE) - self.assertEqual(well.max_volume, 200) + self.assertEqual(well.max_volume, 372.4) # The cavity is 10.80 mm deep: the rim is at the plate height and the floor is 1.20 mm thick. rim = well.get_absolute_location("c", "c", "t").z @@ -51,7 +51,7 @@ def test_height_volume_data(self): self.assertEqual(well.compute_volume_from_height(0.0), 0.0) assert well.height_volume_data is not None self.assertAlmostEqual(max(well.height_volume_data), cavity_depth) - self.assertAlmostEqual(well.compute_volume_from_height(cavity_depth), 372.4) + self.assertEqual(max(well.height_volume_data.values()), well.max_volume) # The well is a truncated cone, 6.40 mm across the floor and 6.85 mm across the rim. def cone_volume(h: float) -> float: @@ -59,9 +59,8 @@ def cone_volume(h: float) -> float: r = r_floor + (r_rim - r_floor) * h / cavity_depth return math.pi * h * (r_floor**2 + r_floor * r + r**2) / 3 - for i in range(109): - h = i / 10 - self.assertAlmostEqual(well.compute_volume_from_height(h), cone_volume(h), delta=0.1) + for h, volume in well.height_volume_data.items(): + self.assertAlmostEqual(volume, cone_volume(h), delta=0.05) self.assertAlmostEqual(well.compute_height_from_volume(200), 5.98, places=2) def test_serialize_round_trip(self): From f24f61b8ad1d992085f4b9394062b7a9863a0321 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Wed, 7 Oct 2026 11:03:10 +0100 Subject: [PATCH 4/4] nest_96_wellplate_200uL_Fb: drop the definition tests The tests restated the definition's own numbers; a pure labware definition needs no such protection. Co-Authored-By: Claude Opus 5.5 --- pylabrobot/resources/nest/plates_tests.py | 68 ----------------------- 1 file changed, 68 deletions(-) delete mode 100644 pylabrobot/resources/nest/plates_tests.py diff --git a/pylabrobot/resources/nest/plates_tests.py b/pylabrobot/resources/nest/plates_tests.py deleted file mode 100644 index 5cbbb5de932..00000000000 --- a/pylabrobot/resources/nest/plates_tests.py +++ /dev/null @@ -1,68 +0,0 @@ -import math -import unittest - -from pylabrobot.resources import Coordinate, Plate, nest_96_wellplate_200uL_Fb -from pylabrobot.resources.well import CrossSectionType, WellBottomType - - -class TestNest96Wellplate200uLFb(unittest.TestCase): - def setUp(self): - self.plate = nest_96_wellplate_200uL_Fb(name="plate") - self.plate.location = Coordinate.zero() - - def test_footprint(self): - self.assertEqual(self.plate.get_size_x(), 127.56) - self.assertEqual(self.plate.get_size_y(), 85.36) - self.assertEqual(self.plate.get_size_z(), 14.30) - - def test_layout(self): - self.assertEqual(self.plate.num_items_x, 12) - self.assertEqual(self.plate.num_items_y, 8) - self.assertEqual(len(self.plate.get_all_items()), 96) - - def test_well_centers(self): - # The data sheet places the center of A1 14.28 mm from the left and 11.18 mm from the back edge, - # at a pitch of 9.00 mm; H12 is 99.00 mm to the right of A1 and 63.00 mm in front of it. - a1 = self.plate.get_well("A1").get_absolute_location("c", "c", "b") - self.assertAlmostEqual(a1.x, 14.28) - self.assertAlmostEqual(a1.y, 85.36 - 11.18) - h12 = self.plate.get_well("H12").get_absolute_location("c", "c", "b") - self.assertAlmostEqual(h12.x, 14.28 + 99.00) - self.assertAlmostEqual(h12.y, 85.36 - 11.18 - 63.00) - - def test_well_geometry(self): - well = self.plate.get_well("A1") - self.assertEqual(well.get_size_x(), 6.85) - self.assertEqual(well.get_size_y(), 6.85) - self.assertEqual(well.bottom_type, WellBottomType.FLAT) - self.assertEqual(well.cross_section_type, CrossSectionType.CIRCLE) - self.assertEqual(well.max_volume, 372.4) - - # The cavity is 10.80 mm deep: the rim is at the plate height and the floor is 1.20 mm thick. - rim = well.get_absolute_location("c", "c", "t").z - cavity_bottom = well.get_absolute_location().z + well.material_z_thickness - self.assertAlmostEqual(rim, 14.30) - self.assertAlmostEqual(cavity_bottom, 3.50) - self.assertAlmostEqual(rim - cavity_bottom, 10.80) - - def test_height_volume_data(self): - well = self.plate.get_well("A1") - cavity_depth = well.get_size_z() - well.material_z_thickness - self.assertEqual(well.compute_volume_from_height(0.0), 0.0) - assert well.height_volume_data is not None - self.assertAlmostEqual(max(well.height_volume_data), cavity_depth) - self.assertEqual(max(well.height_volume_data.values()), well.max_volume) - - # The well is a truncated cone, 6.40 mm across the floor and 6.85 mm across the rim. - def cone_volume(h: float) -> float: - r_floor, r_rim = 6.40 / 2, 6.85 / 2 - r = r_floor + (r_rim - r_floor) * h / cavity_depth - return math.pi * h * (r_floor**2 + r_floor * r + r**2) / 3 - - for h, volume in well.height_volume_data.items(): - self.assertAlmostEqual(volume, cone_volume(h), delta=0.05) - self.assertAlmostEqual(well.compute_height_from_volume(200), 5.98, places=2) - - def test_serialize_round_trip(self): - plate = nest_96_wellplate_200uL_Fb(name="another_plate") - self.assertEqual(Plate.deserialize(plate.serialize()), plate)